boost-skill-cli 1.0.239__tar.gz → 1.0.241__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 (485) hide show
  1. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/PKG-INFO +1 -1
  2. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/_version.py +2 -2
  3. boost_skill_cli-1.0.241/boost_cli/core/journal.py +114 -0
  4. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/util.py +54 -0
  5. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_skill_cli.egg-info/PKG-INFO +1 -1
  6. boost_skill_cli-1.0.241/docs/roadmap/items/journal-rotation-race-and-handle-leak.md +37 -0
  7. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap.html +17 -2
  8. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_journal.py +112 -0
  9. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_util.py +67 -0
  10. boost_skill_cli-1.0.239/boost_cli/core/journal.py +0 -69
  11. boost_skill_cli-1.0.239/docs/roadmap/items/journal-rotation-race-and-handle-leak.md +0 -22
  12. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/.gitattributes +0 -0
  13. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/.gitignore +0 -0
  14. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/.gitleaks.toml +0 -0
  15. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/.htmlvalidate.json +0 -0
  16. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/.lighthouserc.json +0 -0
  17. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/.lycheeignore +0 -0
  18. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/.markdownlint-cli2.jsonc +0 -0
  19. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/.pre-commit-config.yaml +0 -0
  20. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/.stylelintrc.json +0 -0
  21. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/.vale/styles/boost/Terminology.yml +0 -0
  22. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/.vale/styles/config/vocabularies/boost/accept.txt +0 -0
  23. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/.vale.ini +0 -0
  24. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/CLAUDE.md +0 -0
  25. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/CONTRIBUTING.md +0 -0
  26. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/LICENSE +0 -0
  27. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/MANIFEST.in +0 -0
  28. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/Makefile +0 -0
  29. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/README.md +0 -0
  30. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/SECURITY.md +0 -0
  31. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost +0 -0
  32. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/__init__.py +0 -0
  33. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/__main__.py +0 -0
  34. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/cli.py +0 -0
  35. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/cliparse.py +0 -0
  36. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/commands/__init__.py +0 -0
  37. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/commands/_common.py +0 -0
  38. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/commands/bmad.py +0 -0
  39. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/commands/configuration.py +0 -0
  40. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/commands/discovery.py +0 -0
  41. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/commands/hooks.py +0 -0
  42. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/commands/info.py +0 -0
  43. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/commands/intelligence.py +0 -0
  44. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/commands/pkg.py +0 -0
  45. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/commands/quality.py +0 -0
  46. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/commands/run.py +0 -0
  47. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/commands/safety.py +0 -0
  48. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/commands/taps.py +0 -0
  49. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/commands/team.py +0 -0
  50. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/__init__.py +0 -0
  51. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/adapters.py +0 -0
  52. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/agents.py +0 -0
  53. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/ai.py +0 -0
  54. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/capabilities.py +0 -0
  55. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/catalog.py +0 -0
  56. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/claude_settings.py +0 -0
  57. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/config.py +0 -0
  58. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/dense.py +0 -0
  59. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/ed25519.py +0 -0
  60. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/embed.py +0 -0
  61. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/faithfulness.py +0 -0
  62. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/frontmatter.py +0 -0
  63. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/gitutil.py +0 -0
  64. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/imperative.py +0 -0
  65. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/injectscan.py +0 -0
  66. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/integrity.py +0 -0
  67. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/lockfile.py +0 -0
  68. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/logs.py +0 -0
  69. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/mcp.py +0 -0
  70. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/mcpdecl.py +0 -0
  71. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/minisign.py +0 -0
  72. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/nethttp.py +0 -0
  73. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/output.py +0 -0
  74. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/paths.py +0 -0
  75. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/policy.py +0 -0
  76. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/projectlock.py +0 -0
  77. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/provenance.py +0 -0
  78. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/rag.py +0 -0
  79. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/registry.py +0 -0
  80. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/resolve.py +0 -0
  81. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/rules.py +0 -0
  82. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/scopes.py +0 -0
  83. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/secretscan.py +0 -0
  84. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/selfupdate.py +0 -0
  85. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/serve.py +0 -0
  86. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/stackprobe.py +0 -0
  87. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/staleness.py +0 -0
  88. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/store.py +0 -0
  89. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/trustaudit.py +0 -0
  90. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/typosquat.py +0 -0
  91. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/updatediff.py +0 -0
  92. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/core/workflows.py +0 -0
  93. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/data/registries.json +0 -0
  94. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/errors.py +0 -0
  95. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_cli/spin.py +0 -0
  96. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_skill_cli.egg-info/SOURCES.txt +0 -0
  97. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_skill_cli.egg-info/dependency_links.txt +0 -0
  98. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_skill_cli.egg-info/entry_points.txt +0 -0
  99. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_skill_cli.egg-info/requires.txt +0 -0
  100. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/boost_skill_cli.egg-info/top_level.txt +0 -0
  101. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/codecov.yml +0 -0
  102. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/.claude/settings.local.json +0 -0
  103. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/DEBUGGING.md +0 -0
  104. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/adapters.html +0 -0
  105. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/carousel/gifs/distill.gif +0 -0
  106. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/carousel/gifs/doctor.gif +0 -0
  107. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/carousel/gifs/explain.gif +0 -0
  108. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/carousel/gifs/hooks.gif +0 -0
  109. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/carousel/gifs/install.gif +0 -0
  110. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/carousel/gifs/pulse.gif +0 -0
  111. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/carousel/gifs/search.gif +0 -0
  112. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/carousel/gifs/tap.gif +0 -0
  113. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/carousel/tapes/distill.tape +0 -0
  114. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/carousel/tapes/doctor.tape +0 -0
  115. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/carousel/tapes/explain.tape +0 -0
  116. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/carousel/tapes/hooks.tape +0 -0
  117. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/carousel/tapes/install.tape +0 -0
  118. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/carousel/tapes/pulse.tape +0 -0
  119. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/carousel/tapes/search.tape +0 -0
  120. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/carousel/tapes/tap.tape +0 -0
  121. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/carousel.html +0 -0
  122. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/commands.html +0 -0
  123. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/demo.gif +0 -0
  124. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/demo.tape +0 -0
  125. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/design-roadmap.html +0 -0
  126. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/eval.html +0 -0
  127. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/index.html +0 -0
  128. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/mcp-hub.html +0 -0
  129. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/rag-architecture.md +0 -0
  130. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D01.md +0 -0
  131. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D02.md +0 -0
  132. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D03.md +0 -0
  133. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D04.md +0 -0
  134. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D05.md +0 -0
  135. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D06.md +0 -0
  136. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D07.md +0 -0
  137. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D08.md +0 -0
  138. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D09.md +0 -0
  139. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D10.md +0 -0
  140. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D11.md +0 -0
  141. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D12.md +0 -0
  142. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D13.md +0 -0
  143. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D14.md +0 -0
  144. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D15.md +0 -0
  145. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D16.md +0 -0
  146. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D17.md +0 -0
  147. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D18.md +0 -0
  148. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D19.md +0 -0
  149. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D20.md +0 -0
  150. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D21.md +0 -0
  151. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D22.md +0 -0
  152. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D23.md +0 -0
  153. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D24.md +0 -0
  154. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D25.md +0 -0
  155. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/BOOST-D26.md +0 -0
  156. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/accessibility-audit-pa11y-ci-axe-core.md +0 -0
  157. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/adapter-conformance-langgraph-leg-never-ran.md +0 -0
  158. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/ai-bridge-silent-failure-logging.md +0 -0
  159. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/ambiguous-tap-short-name-resolution.md +0 -0
  160. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/atomic-corruption-safe-lock-file-writes.md +0 -0
  161. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/atomic-skill-install-temp-dir-swap.md +0 -0
  162. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/autonomous-ship-workflow-and-isolated-worktree.md +0 -0
  163. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/boost-run-live-agents.md +0 -0
  164. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/bring-commands-under-mutation-testing.md +0 -0
  165. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/broken-link-and-anchor-checking-lychee.md +0 -0
  166. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/browse-crashes-selecting-a-rule-or-workflow.md +0 -0
  167. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/build-provenance-slsa-attestations.md +0 -0
  168. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/cache-the-catalog-entry-set-across-rag-queries.md +0 -0
  169. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/capability-manifest-and-least-privilege-policy.md +0 -0
  170. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/ci-job-timeout-minutes.md +0 -0
  171. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/clean-env-install-smoke-pip-and-pipx.md +0 -0
  172. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/command-carousel-page.md +0 -0
  173. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/command-reference-docs-site.md +0 -0
  174. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/complexity-and-dead-code-radar-xenon-vulture.md +0 -0
  175. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/consolidate-skill-staleness-drift-logic-into-cor.md +0 -0
  176. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/coverage-dashboard-codecov-free-for-oss.md +0 -0
  177. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/coverage-guided-fuzzing-atheris-oss-fuzz.md +0 -0
  178. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/crash-listing-branch-untested.md +0 -0
  179. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/crash-recorder-error-paths-core-logs-py.md +0 -0
  180. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/dense-search-fallback-and-stale-tap-pruning.md +0 -0
  181. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/dependabot-missing-pip-ecosystem.md +0 -0
  182. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/dependency-cve-gate-pip-audit.md +0 -0
  183. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/docsite-chrome-and-content-audit.md +0 -0
  184. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/docstring-coverage-interrogate.md +0 -0
  185. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/doctor-checks-rules-and-workflows.md +0 -0
  186. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/end-of-options-guard-on-git-commands.md +0 -0
  187. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/extension-free-tests-core-dense-py.md +0 -0
  188. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/extract-mcp-http-servers-out-of-configuration-py.md +0 -0
  189. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/finish-mutation-hardening-across-core.md +0 -0
  190. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/fix-self-update-version-detection-dead-branch.md +0 -0
  191. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/fork-safe-network-proxy-handler.md +0 -0
  192. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/framework-adapter-langgraph.md +0 -0
  193. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/framework-adapter-multi-agent.md +0 -0
  194. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/framework-adapters-boost-adapt.md +0 -0
  195. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/frontmatter-scalar-over-coercion.md +0 -0
  196. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/github-community-health-files.md +0 -0
  197. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/github-pages-deploy-is-broken-every-push.md +0 -0
  198. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/golden-set-statistical-power.md +0 -0
  199. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/hash-pinned-reproducible-toolchain-uv-lock.md +0 -0
  200. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/html-validation-html-validate.md +0 -0
  201. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/install-from-path-bypasses-policy-and-pin-checks.md +0 -0
  202. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/install-resolves-skill-dependencies.md +0 -0
  203. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/install-scope-user-or-project.md +0 -0
  204. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/installed-skill-trust-audit.md +0 -0
  205. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/integrity-verification-boost-verify.md +0 -0
  206. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/invocation-pid-logging-for-crash-correlation.md +0 -0
  207. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/layering-guard-import-linter.md +0 -0
  208. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/license-compliance-scanning.md +0 -0
  209. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/lighthouse-ci-on-the-pages-site.md +0 -0
  210. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/lint-tap-misscopes-rule-workflow-entries.md +0 -0
  211. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/list-rules-and-workflows.md +0 -0
  212. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/localize-the-stored-bm25-snippet.md +0 -0
  213. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/lock-invariant-cannot-parse-extras-pins.md +0 -0
  214. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/lockfile-enforcement-and-commit-pinning.md +0 -0
  215. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/log-timestamps-mislabeled-utc.md +0 -0
  216. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/lowest-version-resolution-uv-resolution-lowest-d.md +0 -0
  217. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/main-has-no-branch-protection.md +0 -0
  218. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/make-the-roadmaps-discoverable.md +0 -0
  219. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/markdown-consistency-markdownlint-cli2.md +0 -0
  220. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/mcp-aware-skills.md +0 -0
  221. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/mcp-hub-diagram-node-text-overflow.md +0 -0
  222. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/mcp-launch-objc-fork-safety.md +0 -0
  223. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/mcp-register-names-server-before-env.md +0 -0
  224. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/mcp-search-before-reinventing.md +0 -0
  225. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/mcp-startup-self-harden-fork-safety.md +0 -0
  226. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/memoize-config-load-in-process.md +0 -0
  227. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/modernization-smells-refurb-pyupgrade.md +0 -0
  228. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/mutation-hardening-core-frontmatter-py.md +0 -0
  229. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/mutation-hardening-core-gitutil-py.md +0 -0
  230. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/mutation-hardening-core-store-py.md +0 -0
  231. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/mypy-strict-mode-for-commands.md +0 -0
  232. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/negative-limit-inverts-log-pulse-output.md +0 -0
  233. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/onboard-overwrites-generated-files-without-confirm.md +0 -0
  234. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/one-command-every-env-nox.md +0 -0
  235. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/one-shared-atomic-write-helper.md +0 -0
  236. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/package-metadata-validation-twine-check-friends.md +0 -0
  237. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/patch-coverage-gate-diff-cover.md +0 -0
  238. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/path-traversal-unsanitized-rule-workflow-name.md +0 -0
  239. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/performance-regression-gate-pytest-benchmark.md +0 -0
  240. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/pin-the-lint-toolchain.md +0 -0
  241. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/post-deploy-smoke-headless-load-check.md +0 -0
  242. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/pre-release-python-canary-3-14t-free-threaded.md +0 -0
  243. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/project-scope-across-every-command.md +0 -0
  244. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/project-scope-symlink-escape-on-write.md +0 -0
  245. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/promote-nav-footer-into-the-shared-style-system.md +0 -0
  246. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/prompt-injection-scanning-of-skill-markdown.md +0 -0
  247. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/property-based-tests-hypothesis-on-the-parsers.md +0 -0
  248. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/prose-and-terminology-linting-vale.md +0 -0
  249. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/prune-ignored-dirs-during-scan-dir-walk.md +0 -0
  250. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/publish-gate-ignores-pip-audit-and-metadata.md +0 -0
  251. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/pytest-tmpdir-cve-blocked-by-py39-floor.md +0 -0
  252. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/quality-dashboard-sonarcloud-free-for-oss.md +0 -0
  253. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/ranx-significance-monitor.md +0 -0
  254. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/reconcile-the-theme-drift.md +0 -0
  255. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/refresh-the-marketing-surface.md +0 -0
  256. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/rel-time-tests-race-the-wall-clock.md +0 -0
  257. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/required-checks-can-declare-a-check-that-deadlocks-prs.md +0 -0
  258. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/required-checks-config-drift.md +0 -0
  259. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/retrieval-quality-eval-harness.md +0 -0
  260. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/reuse-helpers-kill-minor-dead-work.md +0 -0
  261. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/robust-tag-argument-parsing.md +0 -0
  262. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/ruff-016-widens-the-default-rule-set.md +0 -0
  263. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/rule-install-native-materialization.md +0 -0
  264. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/runner-egress-monitoring-stepsecurity-harden-run.md +0 -0
  265. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/runtime-explain-faithfulness-guardrail.md +0 -0
  266. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/sbom-aware-scanning-osv-scanner.md +0 -0
  267. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/sbom-on-every-release-cyclonedx-syft.md +0 -0
  268. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/scan-and-sync-rules-and-workflows.md +0 -0
  269. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/scorecard-findings-triage.md +0 -0
  270. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/second-type-checker-pyright.md +0 -0
  271. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/secret-and-pii-scanning-of-installed-skills.md +0 -0
  272. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/secret-scanning-gitleaks-push-protection.md +0 -0
  273. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/security-linting-bandit-via-ruff-s-rules.md +0 -0
  274. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/self-update-broken-for-pip-pipx-installs.md +0 -0
  275. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/serve-py-traversal-guards-untested.md +0 -0
  276. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/shift-left-gate-pre-commit-pre-commit-ci.md +0 -0
  277. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/single-imperative-rule-extractor.md +0 -0
  278. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/single-tech-stack-prober.md +0 -0
  279. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/split-oversized-command-modules.md +0 -0
  280. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/startup-and-import-time-budget-x-importtime.md +0 -0
  281. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/stop-re-serializing-entry-meta-on-every-search.md +0 -0
  282. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/structured-json-log-output-mode.md +0 -0
  283. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/supply-chain-posture-openssf-scorecard.md +0 -0
  284. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/surface-every-docs-page-from-the-guide.md +0 -0
  285. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/sync-deletes-unowned-broken-symlinks.md +0 -0
  286. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/tap-signing-and-provenance-sigstore-minisign.md +0 -0
  287. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/theme-asset-linting-stylelint-eslint.md +0 -0
  288. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/typo-detection-codespell.md +0 -0
  289. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/typosquat-and-name-confusion-detection.md +0 -0
  290. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/unify-tilde-two-copies-have-a-boundary-bug.md +0 -0
  291. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/uninstall-has-no-confirmation-prompt.md +0 -0
  292. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/untrack-generated-build-noise.md +0 -0
  293. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/update-diff-before-apply.md +0 -0
  294. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/update-refreshes-rules-and-workflows.md +0 -0
  295. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/update-reinstall-widens-agent-scope.md +0 -0
  296. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/visual-regression-pass-on-the-guide.md +0 -0
  297. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/widen-the-ruff-rule-surface-b-sim-c4-perf-ruf.md +0 -0
  298. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/windows-in-the-ci-matrix.md +0 -0
  299. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/wire-bdd-suite-into-ci.md +0 -0
  300. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/workflow-install-native-materialization.md +0 -0
  301. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/workflow-linting-actionlint.md +0 -0
  302. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/workflow-sast-zizmor.md +0 -0
  303. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/docs/roadmap/items/workspace-scope-install.md +0 -0
  304. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/eslint.config.mjs +0 -0
  305. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/examples/README.md +0 -0
  306. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/examples/adapt-demo.sh +0 -0
  307. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/examples/boost-run-prototype.sh +0 -0
  308. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/index.html +0 -0
  309. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/noxfile.py +0 -0
  310. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/pyproject.toml +0 -0
  311. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/requirements/coverage-tools.in +0 -0
  312. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/requirements/coverage-tools.txt +0 -0
  313. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/requirements/lint-tools.in +0 -0
  314. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/requirements/lint-tools.txt +0 -0
  315. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/requirements/mutation-tools.in +0 -0
  316. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/requirements/mutation-tools.txt +0 -0
  317. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/requirements/release-tools.in +0 -0
  318. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/requirements/release-tools.txt +0 -0
  319. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/requirements/test-tools.in +0 -0
  320. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/requirements/test-tools.txt +0 -0
  321. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/a11y_check.py +0 -0
  322. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/bench_cli.sh +0 -0
  323. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/build_command_reference.py +0 -0
  324. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/build_registries.py +0 -0
  325. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/build_roadmap.py +0 -0
  326. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/check_anchors.py +0 -0
  327. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/check_required_checks.py +0 -0
  328. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/ensure_eval_corpus.sh +0 -0
  329. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/eval_explain.py +0 -0
  330. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/eval_recommend.py +0 -0
  331. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/eval_retrieval.py +0 -0
  332. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/eval_stats_summary.py +0 -0
  333. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/import_budget.py +0 -0
  334. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/lock_toolchain.py +0 -0
  335. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/mutation_gate.py +0 -0
  336. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/perf_gate.py +0 -0
  337. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/post_deploy_smoke.py +0 -0
  338. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/scripts/release_preflight.py +0 -0
  339. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/setup.cfg +0 -0
  340. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/sonar-project.properties +0 -0
  341. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/style/README.md +0 -0
  342. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/style/boost.css +0 -0
  343. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/style/boost.js +0 -0
  344. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/style/demo.html +0 -0
  345. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/bdd/features/adapt.feature +0 -0
  346. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/bdd/features/browse.feature +0 -0
  347. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/bdd/features/count.feature +0 -0
  348. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/bdd/features/discover.feature +0 -0
  349. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/bdd/features/doctor.feature +0 -0
  350. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/bdd/features/environment.py +0 -0
  351. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/bdd/features/explain.feature +0 -0
  352. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/bdd/features/index.feature +0 -0
  353. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/bdd/features/install.feature +0 -0
  354. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/bdd/features/list.feature +0 -0
  355. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/bdd/features/mcp.feature +0 -0
  356. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/bdd/features/search.feature +0 -0
  357. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/bdd/features/steps/cli_steps.py +0 -0
  358. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/bdd/features/steps/discovery_steps.py +0 -0
  359. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/bdd/features/steps/mcp_steps.py +0 -0
  360. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/bdd/features/steps/quality_steps.py +0 -0
  361. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/conftest.py +0 -0
  362. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/eval/baseline.json +0 -0
  363. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/eval/explain.jsonl +0 -0
  364. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/eval/golden.jsonl +0 -0
  365. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/eval/recommend.jsonl +0 -0
  366. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/eval/taps.txt +0 -0
  367. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_capabilities_policy.py +0 -0
  368. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_cli_adapt.py +0 -0
  369. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_cli_bmad.py +0 -0
  370. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_cli_configuration.py +0 -0
  371. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_cli_discovery.py +0 -0
  372. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_cli_hooks.py +0 -0
  373. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_cli_info.py +0 -0
  374. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_cli_intelligence.py +0 -0
  375. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_cli_pkg.py +0 -0
  376. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_cli_quality.py +0 -0
  377. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_cli_taps.py +0 -0
  378. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_cli_team.py +0 -0
  379. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_everyday_loop.py +0 -0
  380. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_integrity_enforce.py +0 -0
  381. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_property_parsers.py +0 -0
  382. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_run.py +0 -0
  383. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_tap_signing.py +0 -0
  384. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/functional/test_workspace_scope.py +0 -0
  385. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/README.md +0 -0
  386. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/frontmatter/01-typical.md +0 -0
  387. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/frontmatter/02-lossy-version.md +0 -0
  388. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/frontmatter/03-lossy-numbers.md +0 -0
  389. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/frontmatter/04-lists.md +0 -0
  390. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/frontmatter/05-nested.md +0 -0
  391. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/frontmatter/06-empty-block.md +0 -0
  392. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/frontmatter/07-unterminated.md +0 -0
  393. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/frontmatter/08-none.md +0 -0
  394. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/frontmatter/09-unbalanced-quote.md +0 -0
  395. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/frontmatter/10-keywords.md +0 -0
  396. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/frontmatter/11-crlf.md +0 -0
  397. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/frontmatter/12-bom.md +0 -0
  398. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/registry/01-shorthand.txt +0 -0
  399. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/registry/02-https.txt +0 -0
  400. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/registry/03-ssh.txt +0 -0
  401. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/registry/04-traversal.txt +0 -0
  402. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/registry/05-empty.txt +0 -0
  403. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/registry/06-space.txt +0 -0
  404. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/registry/07-slash.txt +0 -0
  405. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/corpus/registry/08-bare-host.txt +0 -0
  406. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/fuzz_frontmatter.py +0 -0
  407. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/fuzz/fuzz_registry.py +0 -0
  408. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/make_fixture.py +0 -0
  409. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/perf/test_benchmarks.py +0 -0
  410. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/smoke.sh +0 -0
  411. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_a11y_check.py +0 -0
  412. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_adapters.py +0 -0
  413. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_adapters_multi.py +0 -0
  414. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_adapters_runner.py +0 -0
  415. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_agents.py +0 -0
  416. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_ai.py +0 -0
  417. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_capabilities.py +0 -0
  418. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_catalog.py +0 -0
  419. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_check_anchors.py +0 -0
  420. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_claude_settings.py +0 -0
  421. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_cliparse.py +0 -0
  422. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_command_reference_fresh.py +0 -0
  423. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_config.py +0 -0
  424. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_dense.py +0 -0
  425. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_dense_fallback.py +0 -0
  426. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_docs_pages_linked.py +0 -0
  427. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_docsite_chrome.py +0 -0
  428. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_ed25519.py +0 -0
  429. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_embed.py +0 -0
  430. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_errors_and_cli_table.py +0 -0
  431. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_eval_stats_summary.py +0 -0
  432. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_faithfulness.py +0 -0
  433. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_frontmatter.py +0 -0
  434. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_fuzz_targets.py +0 -0
  435. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_gitleaks_config.py +0 -0
  436. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_gitutil.py +0 -0
  437. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_imperative.py +0 -0
  438. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_import_budget.py +0 -0
  439. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_injectscan.py +0 -0
  440. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_integrity.py +0 -0
  441. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_lockfile.py +0 -0
  442. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_logs.py +0 -0
  443. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_marketing_counts.py +0 -0
  444. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_mcp.py +0 -0
  445. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_mcpdecl.py +0 -0
  446. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_minisign.py +0 -0
  447. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_mutation_hardening.py +0 -0
  448. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_nethttp.py +0 -0
  449. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_output.py +0 -0
  450. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_paths.py +0 -0
  451. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_perf_gate.py +0 -0
  452. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_pkg_confusions.py +0 -0
  453. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_pkg_injection.py +0 -0
  454. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_pkg_secrets.py +0 -0
  455. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_policy.py +0 -0
  456. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_post_deploy_smoke.py +0 -0
  457. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_projectlock.py +0 -0
  458. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_provenance.py +0 -0
  459. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_rag.py +0 -0
  460. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_registries_fresh.py +0 -0
  461. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_registry.py +0 -0
  462. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_release_preflight.py +0 -0
  463. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_required_checks.py +0 -0
  464. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_resolve.py +0 -0
  465. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_roadmap_fresh.py +0 -0
  466. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_rules.py +0 -0
  467. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_scopes.py +0 -0
  468. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_secretscan.py +0 -0
  469. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_selfupdate.py +0 -0
  470. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_serve.py +0 -0
  471. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_spin.py +0 -0
  472. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_stackprobe.py +0 -0
  473. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_staleness.py +0 -0
  474. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_store.py +0 -0
  475. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_token_parity.py +0 -0
  476. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_toolchain_lock.py +0 -0
  477. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_trustaudit.py +0 -0
  478. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_typosquat.py +0 -0
  479. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_updatediff.py +0 -0
  480. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_version.py +0 -0
  481. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/unit/test_workflows.py +0 -0
  482. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/visual/a11y_check.mjs +0 -0
  483. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/visual/console_check.mjs +0 -0
  484. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/visual/package.json +0 -0
  485. {boost_skill_cli-1.0.239 → boost_skill_cli-1.0.241}/tests/visual/visual_check.mjs +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boost-skill-cli
3
- Version: 1.0.239
3
+ Version: 1.0.241
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.239'
22
- __version_tuple__ = version_tuple = (1, 0, 239)
21
+ __version__ = version = '1.0.241'
22
+ __version_tuple__ = version_tuple = (1, 0, 241)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -0,0 +1,114 @@
1
+ """The pulse journal: append-only feed of skill-management events.
2
+
3
+ Powers `boost pulse`, `boost trending`, `boost stats`, and `boost who`.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ from typing import List, Optional
9
+
10
+ from . import paths, util
11
+
12
+ ROTATE_AT = 5000
13
+ ROTATE_KEEP = 2500
14
+
15
+
16
+ def log(action: str, subject: str = "", **fields) -> None:
17
+ """Append one event line to the pulse feed, rotating when oversized.
18
+
19
+ None-valued keyword fields are dropped from the record.
20
+ """
21
+ paths.ensure_dirs()
22
+ event = {"ts": util.now_iso(), "user": util.user(), "action": action,
23
+ "subject": subject}
24
+ event.update({k: v for k, v in fields.items() if v is not None})
25
+ p = paths.pulse_path()
26
+ with p.open("a") as f:
27
+ f.write(json.dumps(event) + "\n")
28
+ _maybe_rotate()
29
+
30
+
31
+ def events(n: Optional[int] = None, action: Optional[str] = None,
32
+ subject: Optional[str] = None) -> List[dict]:
33
+ """Most-recent-first list of journal events."""
34
+ p = paths.pulse_path()
35
+ if not p.exists():
36
+ return []
37
+ out = []
38
+ # errors="replace": one torn append must not make the whole feed
39
+ # unreadable — the corrupt line simply fails to parse as JSON below.
40
+ for line in p.read_text(encoding="utf-8", errors="replace").splitlines():
41
+ try:
42
+ e = json.loads(line)
43
+ except json.JSONDecodeError:
44
+ continue
45
+ if action and e.get("action") != action:
46
+ continue
47
+ if subject and e.get("subject") != subject:
48
+ continue
49
+ out.append(e)
50
+ out.reverse()
51
+ return out[:n] if n else out
52
+
53
+
54
+ def _line_count(p) -> int:
55
+ """Lines in `p`, closing the handle rather than leaving it to the GC.
56
+
57
+ Decodes leniently: the feed carries user-supplied text and a torn append
58
+ can leave a partial multi-byte sequence behind. A strict decode raises
59
+ UnicodeDecodeError, which is a ValueError — it would sail past the OSError
60
+ guard below and take down every command that logs, forever, over one bad
61
+ byte in an advisory feed.
62
+ """
63
+ with p.open(encoding="utf-8", errors="replace") as f:
64
+ return sum(1 for _ in f)
65
+
66
+
67
+ def rotation_healthy() -> bool:
68
+ """True when the pulse feed is absent or at most ROTATE_AT lines long."""
69
+ p = paths.pulse_path()
70
+ if not p.exists():
71
+ return True
72
+ return _line_count(p) <= ROTATE_AT
73
+
74
+
75
+ def _maybe_rotate() -> None:
76
+ """Trim the feed to ROTATE_KEEP lines once it passes ROTATE_AT.
77
+
78
+ Rotation is a read-modify-write of the whole file, and this repo's own
79
+ working model expects concurrent boost processes. Unserialized, two of
80
+ them could each read the feed, each build a snapshot, and the second
81
+ write would silently drop whatever the first had appended in between.
82
+
83
+ So rotation runs under a lock, and a process that cannot take it simply
84
+ returns: another one is already trimming, and doing it twice adds nothing.
85
+ Inside the lock the file is re-read — the count that got us here was taken
86
+ outside it and may already be stale — and anything appended between that
87
+ read and the swap is carried into the new file rather than dropped.
88
+ `log()` itself stays lock-free: O_APPEND writes of a single short record
89
+ do not tear, and making every command contend on a lock to protect an
90
+ advisory feed would be a bad trade.
91
+ """
92
+ p = paths.pulse_path()
93
+ try:
94
+ if _line_count(p) <= ROTATE_AT:
95
+ return
96
+ except OSError:
97
+ return
98
+ with util.try_lock(p.with_name(p.name + ".rotate.lock")) as locked:
99
+ if not locked:
100
+ return
101
+ try:
102
+ raw = p.read_bytes()
103
+ lines = raw.decode("utf-8", "replace").splitlines()
104
+ if len(lines) <= ROTATE_AT:
105
+ return # someone else already rotated
106
+ keep = lines[-ROTATE_KEEP:]
107
+ # Re-read past the point we stopped, so an append that landed
108
+ # while we were building the snapshot survives it.
109
+ with p.open("rb") as f:
110
+ f.seek(len(raw))
111
+ keep += f.read().decode("utf-8", "replace").splitlines()
112
+ except OSError:
113
+ return
114
+ util.atomic_write_text(p, "\n".join(keep) + "\n")
@@ -10,6 +10,7 @@ import shutil
10
10
  import stat
11
11
  import sys
12
12
  import tempfile
13
+ import time
13
14
  from datetime import datetime, timezone
14
15
  from pathlib import Path
15
16
  from typing import List, Tuple
@@ -37,6 +38,59 @@ def rmtree(path) -> None:
37
38
  shutil.rmtree(str(path), onerror=_clear_readonly_and_retry)
38
39
 
39
40
 
41
+ def _lock_is_stale(path: Path, stale_after: float) -> bool:
42
+ """True when a lock file is old enough to have been abandoned."""
43
+ try:
44
+ return (time.time() - path.stat().st_mtime) > stale_after
45
+ except OSError:
46
+ return True # it vanished under us — treat it as free
47
+
48
+
49
+ @contextlib.contextmanager
50
+ def try_lock(path, stale_after: float = 300.0):
51
+ """Best-effort exclusive lock, yielding whether it was actually taken.
52
+
53
+ An `O_CREAT | O_EXCL` create is atomic on every filesystem boost targets,
54
+ and needs neither `fcntl` (missing on Windows) nor `msvcrt` (missing
55
+ everywhere else). Yields True to the holder and **False** to everyone
56
+ else — this does not wait, because its callers are jobs where "another
57
+ process is already doing it" is a perfectly good outcome, and blocking
58
+ would trade a rare lost update for a common stall.
59
+
60
+ A lock file older than `stale_after` is stolen: a process killed
61
+ mid-operation must not wedge the operation forever. Any error acquiring
62
+ it yields False rather than raising — failing to take a lock must never
63
+ be worse than the problem it guards.
64
+ """
65
+ path = Path(path)
66
+ fd = None
67
+ try:
68
+ path.parent.mkdir(parents=True, exist_ok=True)
69
+ try:
70
+ fd = os.open(str(path), os.O_CREAT | os.O_EXCL | os.O_WRONLY)
71
+ except FileExistsError:
72
+ if not _lock_is_stale(path, stale_after):
73
+ yield False
74
+ return
75
+ with contextlib.suppress(OSError):
76
+ path.unlink()
77
+ fd = os.open(str(path), os.O_CREAT | os.O_EXCL | os.O_WRONLY)
78
+ except OSError:
79
+ yield False
80
+ return
81
+ try:
82
+ with contextlib.suppress(OSError), os.fdopen(fd, "w") as f:
83
+ fd = None # fdopen owns it now
84
+ f.write(str(os.getpid()))
85
+ yield True
86
+ finally:
87
+ if fd is not None:
88
+ with contextlib.suppress(OSError):
89
+ os.close(fd)
90
+ with contextlib.suppress(OSError):
91
+ path.unlink()
92
+
93
+
40
94
  def atomic_write_text(path: Path, text: str, encoding: str = "utf-8") -> None:
41
95
  """Write ``text`` to ``path`` so a reader never sees a partial file.
42
96
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boost-skill-cli
3
- Version: 1.0.239
3
+ Version: 1.0.241
4
4
  Summary: boost — Homebrew for AI coding skills
5
5
  Author: Jonathan Reyes
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -0,0 +1,37 @@
1
+ ---
2
+ id: journal-rotation-race-and-handle-leak
3
+ board: code
4
+ section: internals
5
+ status: shipped
6
+ category: Concurrency · Bug
7
+ complexity: M
8
+ impact: Med
9
+ wow: 2
10
+ note:
11
+ order: 31
12
+ owner: loop/journal-rotation
13
+ pr: 275
14
+ title: Journal rotation has a lost-update race between concurrent processes
15
+ ---
16
+ <code>_maybe_rotate</code> reads the whole pulse file and atomically replaces it with a truncated
17
+ snapshot — but two concurrent boost processes (explicitly expected per this repo's parallel-loop
18
+ model) can both read, then both write their own snapshot, and whichever writes last silently
19
+ discards any event the other appended in between. <code>rotation_healthy()</code> also opens the
20
+ file with a bare <code>p.open()</code> and never closes it, relying on GC instead of a
21
+ <code>with</code> block like every other open in the module. Rotate under a lock (or an
22
+ append-only rename scheme), and close the handle explicitly.
23
+ Shipped with <code>util.try_lock()</code>, a portable <code>O_CREAT | O_EXCL</code> advisory
24
+ lock that needs neither <code>fcntl</code> (absent on Windows) nor <code>msvcrt</code> (absent
25
+ everywhere else). It <em>yields False rather than waiting</em>: a process that cannot take it
26
+ just returns, because another one is already trimming and blocking would trade a rare lost
27
+ update for a common stall. A lock older than five minutes is stolen, so a process killed
28
+ mid-rotation cannot wedge the feed forever. Inside the lock the file is re-read (the count
29
+ that got us there was taken outside it) and anything appended since is carried into the new
30
+ file instead of dropped. <code>log()</code> stays lock-free — <code>O_APPEND</code> writes of
31
+ one short record do not tear, and making every command contend on a lock to protect an
32
+ advisory feed would be the wrong trade.
33
+ Writing the test for a torn append turned up a third defect neither the card nor the code
34
+ had noticed: a single invalid byte in the feed raised <code>UnicodeDecodeError</code>, which
35
+ is a <code>ValueError</code> and sails straight past the <code>except OSError</code> guard —
36
+ so one bad byte crashed <em>every command that logs</em>, permanently. Both reads decode with
37
+ <code>errors="replace"</code> now.
@@ -1014,7 +1014,7 @@ live hits," and prune removed taps on every build, not just changed ones.</p>
1014
1014
  </article>
1015
1015
 
1016
1016
  <article class="cap rcard" id="journal-rotation-race-and-handle-leak">
1017
- <div class="head"><span class="pill planned">Planned</span><span class="cat">Concurrency · Bug</span></div>
1017
+ <div class="head"><span class="pill shipped">Shipped</span><span class="cat">Concurrency · Bug</span></div>
1018
1018
  <h3>Journal rotation has a lost-update race between concurrent processes</h3>
1019
1019
  <p><code>_maybe_rotate</code> reads the whole pulse file and atomically replaces it with a truncated
1020
1020
  snapshot — but two concurrent boost processes (explicitly expected per this repo's parallel-loop
@@ -1022,7 +1022,22 @@ model) can both read, then both write their own snapshot, and whichever writes l
1022
1022
  discards any event the other appended in between. <code>rotation_healthy()</code> also opens the
1023
1023
  file with a bare <code>p.open()</code> and never closes it, relying on GC instead of a
1024
1024
  <code>with</code> block like every other open in the module. Rotate under a lock (or an
1025
- append-only rename scheme), and close the handle explicitly.</p>
1025
+ append-only rename scheme), and close the handle explicitly.
1026
+ Shipped with <code>util.try_lock()</code>, a portable <code>O_CREAT | O_EXCL</code> advisory
1027
+ lock that needs neither <code>fcntl</code> (absent on Windows) nor <code>msvcrt</code> (absent
1028
+ everywhere else). It <em>yields False rather than waiting</em>: a process that cannot take it
1029
+ just returns, because another one is already trimming and blocking would trade a rare lost
1030
+ update for a common stall. A lock older than five minutes is stolen, so a process killed
1031
+ mid-rotation cannot wedge the feed forever. Inside the lock the file is re-read (the count
1032
+ that got us there was taken outside it) and anything appended since is carried into the new
1033
+ file instead of dropped. <code>log()</code> stays lock-free — <code>O_APPEND</code> writes of
1034
+ one short record do not tear, and making every command contend on a lock to protect an
1035
+ advisory feed would be the wrong trade.
1036
+ Writing the test for a torn append turned up a third defect neither the card nor the code
1037
+ had noticed: a single invalid byte in the feed raised <code>UnicodeDecodeError</code>, which
1038
+ is a <code>ValueError</code> and sails straight past the <code>except OSError</code> guard —
1039
+ so one bad byte crashed <em>every command that logs</em>, permanently. Both reads decode with
1040
+ <code>errors="replace"</code> now.</p>
1026
1041
  <div class="meta">
1027
1042
  <span class="m">Complexity <b>M</b></span>
1028
1043
  <span class="m">Impact <b>Med</b></span>
@@ -117,6 +117,118 @@ class TestRotation:
117
117
  assert first["subject"] == "s2502"
118
118
 
119
119
 
120
+ class TestRotationConcurrency:
121
+ """Rotation is a read-modify-write, and this repo expects concurrent
122
+ boost processes. Every assertion here is about not losing an event."""
123
+
124
+ def _lock_path(self):
125
+ p = paths.pulse_path()
126
+ return p.with_name(p.name + ".rotate.lock")
127
+
128
+ def test_a_held_lock_skips_rotation_entirely(self, sandbox):
129
+ # Another process is already trimming. Doing it a second time from a
130
+ # stale read is exactly how an event goes missing.
131
+ write_lines(5001)
132
+ paths.ensure_dirs()
133
+ self._lock_path().write_text("999999", encoding="utf-8")
134
+ journal._maybe_rotate()
135
+ assert len(paths.pulse_path().read_text(
136
+ encoding="utf-8").splitlines()) == 5001
137
+
138
+ def test_the_lock_is_released_after_rotating(self, sandbox):
139
+ write_lines(5001)
140
+ journal._maybe_rotate()
141
+ assert not self._lock_path().exists()
142
+ assert len(paths.pulse_path().read_text(
143
+ encoding="utf-8").splitlines()) == journal.ROTATE_KEEP
144
+
145
+ def test_an_append_during_the_snapshot_survives_it(self, sandbox,
146
+ monkeypatch):
147
+ # The lost update: a concurrent log() lands after rotation has read
148
+ # the feed but before it swaps the trimmed file into place. The old
149
+ # code wrote its stale snapshot over the top and the event vanished.
150
+ write_lines(5001)
151
+ p = paths.pulse_path()
152
+ real_read_bytes = type(p).read_bytes
153
+
154
+ def read_then_someone_appends(self, *a, **kw):
155
+ # Scoped to the pulse file: this patches a method every Path in
156
+ # the process shares, so an unscoped version hands some unrelated
157
+ # read the feed's bytes and appends to the feed behind its back.
158
+ data = real_read_bytes(self, *a, **kw)
159
+ if self == p:
160
+ with p.open("a", encoding="utf-8") as f:
161
+ f.write(json.dumps({"ts": "2026-01-01T00:00:00Z",
162
+ "user": "u", "action": "install",
163
+ "subject": "raced-in"}) + "\n")
164
+ return data
165
+
166
+ monkeypatch.setattr(type(p), "read_bytes", read_then_someone_appends)
167
+ journal._maybe_rotate()
168
+ subjects = [json.loads(line)["subject"] for line
169
+ in p.read_text(encoding="utf-8").splitlines()]
170
+ assert "raced-in" in subjects
171
+ assert subjects[-1] == "raced-in" # and in the right order
172
+
173
+ def test_a_second_rotation_inside_the_lock_is_a_no_op(self, sandbox,
174
+ monkeypatch):
175
+ # The count that gets us into the lock is taken outside it. If another
176
+ # process rotated in between, the re-read must notice and leave the
177
+ # already-trimmed file alone rather than trim it again.
178
+ write_lines(5001)
179
+ p = paths.pulse_path()
180
+ real_read_bytes = type(p).read_bytes
181
+
182
+ def rotated_by_someone_else(self, *a, **kw):
183
+ if self == p:
184
+ # Exactly at the threshold, the boundary that decides it:
185
+ # ROTATE_AT lines is healthy, ROTATE_AT + 1 is not.
186
+ write_lines(journal.ROTATE_AT, action="fresh")
187
+ return real_read_bytes(self, *a, **kw)
188
+
189
+ monkeypatch.setattr(journal, "_line_count", lambda _p: 5001)
190
+ monkeypatch.setattr(type(p), "read_bytes", rotated_by_someone_else)
191
+ journal._maybe_rotate()
192
+ assert len(p.read_text(encoding="utf-8").splitlines()) == journal.ROTATE_AT
193
+
194
+ def test_undecodable_bytes_do_not_abort_rotation(self, sandbox):
195
+ # The feed carries user-supplied text (skill names, tap names), and a
196
+ # torn append can leave a partial multi-byte sequence behind. Rotation
197
+ # must trim the file anyway rather than raise and let it grow forever.
198
+ write_lines(5001)
199
+ p = paths.pulse_path()
200
+ with p.open("ab") as f:
201
+ f.write(b'{"ts": "2026-01-01T00:00:00Z", "user": "u", '
202
+ b'"action": "install", "subject": "caf\xe9"}\n')
203
+ journal._maybe_rotate()
204
+ lines = p.read_text(encoding="utf-8").splitlines()
205
+ assert len(lines) == journal.ROTATE_KEEP
206
+ assert "caf" in lines[-1] # kept, with the bad byte replaced
207
+
208
+ def test_rotation_healthy_closes_its_handle(self, sandbox, monkeypatch):
209
+ # It used to be a bare `p.open()` left to the garbage collector — the
210
+ # one open in the module without a `with`. Asserted by holding a
211
+ # reference to every handle it opens rather than by watching for a
212
+ # ResourceWarning: that warning is raised during finalization, where
213
+ # pytest reports it as unraisable instead of failing the test, so it
214
+ # would not have caught this.
215
+ write_lines(3)
216
+ opened = []
217
+ path_type = type(paths.pulse_path())
218
+ real_open = path_type.open
219
+
220
+ def tracking_open(self, *a, **kw):
221
+ handle = real_open(self, *a, **kw)
222
+ if self == paths.pulse_path(): # only the feed's handles are ours
223
+ opened.append(handle)
224
+ return handle
225
+
226
+ monkeypatch.setattr(path_type, "open", tracking_open)
227
+ assert journal.rotation_healthy() is True
228
+ assert opened, "expected rotation_healthy to read the feed"
229
+ assert all(handle.closed for handle in opened)
230
+
231
+
120
232
  class TestFallbacks:
121
233
  def test_user_falls_back_to_unknown(self, sandbox, monkeypatch):
122
234
  def no_user():
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
 
4
4
  import hashlib
5
5
  import re
6
+ import time
6
7
  from datetime import datetime, timedelta, timezone
7
8
 
8
9
  import pytest
@@ -533,6 +534,72 @@ class TestAtomicWriteText:
533
534
  assert leftovers == []
534
535
 
535
536
 
537
+ class TestTryLock:
538
+ """A lock that silently fails open is worse than no lock at all, so both
539
+ answers are pinned: who gets True, who gets False, and what is left behind."""
540
+
541
+ def test_taken_lock_yields_true_and_cleans_up(self, tmp_path):
542
+ lock = tmp_path / "x.lock"
543
+ with util.try_lock(lock) as got:
544
+ assert got is True
545
+ assert lock.is_file()
546
+ assert not lock.exists()
547
+
548
+ def test_second_holder_is_refused_rather_than_queued(self, tmp_path):
549
+ lock = tmp_path / "x.lock"
550
+ with util.try_lock(lock) as first, util.try_lock(lock) as second:
551
+ assert first is True
552
+ assert second is False
553
+
554
+ def test_the_loser_does_not_delete_the_winners_lock(self, tmp_path):
555
+ # The nastiest failure mode: a refused caller cleaning up on its way
556
+ # out would hand the lock to whoever asked next, while the holder is
557
+ # still working.
558
+ lock = tmp_path / "x.lock"
559
+ with util.try_lock(lock):
560
+ with util.try_lock(lock) as second:
561
+ assert second is False
562
+ assert lock.is_file()
563
+
564
+ def test_lock_is_released_when_the_body_raises(self, tmp_path):
565
+ lock = tmp_path / "x.lock"
566
+ with pytest.raises(ValueError), util.try_lock(lock):
567
+ raise ValueError("boom")
568
+ assert not lock.exists()
569
+
570
+ def test_stale_lock_is_stolen(self, tmp_path):
571
+ import os as _os
572
+ lock = tmp_path / "x.lock"
573
+ lock.write_text("999999", encoding="utf-8")
574
+ old = time.time() - 600
575
+ _os.utime(lock, (old, old))
576
+ with util.try_lock(lock, stale_after=300.0) as got:
577
+ assert got is True # the holder died; don't wedge forever
578
+
579
+ def test_fresh_lock_is_not_stolen(self, tmp_path):
580
+ lock = tmp_path / "x.lock"
581
+ lock.write_text("999999", encoding="utf-8")
582
+ with util.try_lock(lock, stale_after=300.0) as got:
583
+ assert got is False
584
+ assert lock.is_file() # and it is left for its owner
585
+
586
+ def test_a_vanished_lock_reads_as_free(self, tmp_path):
587
+ assert util._lock_is_stale(tmp_path / "never-existed", 300.0) is True
588
+
589
+ def test_unusable_path_refuses_instead_of_raising(self, tmp_path):
590
+ # Failing to take a lock must never be worse than the race it guards.
591
+ blocker = tmp_path / "not-a-dir"
592
+ blocker.write_text("x", encoding="utf-8")
593
+ with util.try_lock(blocker / "sub" / "x.lock") as got:
594
+ assert got is False
595
+
596
+ def test_the_holder_records_its_pid(self, tmp_path):
597
+ import os as _os
598
+ lock = tmp_path / "x.lock"
599
+ with util.try_lock(lock):
600
+ assert lock.read_text(encoding="utf-8") == str(_os.getpid())
601
+
602
+
536
603
  class TestSafeComponent:
537
604
  """A catalog name becomes a path component, so it is attacker-controlled
538
605
  input on the way to an install path (a tap writes its own frontmatter)."""
@@ -1,69 +0,0 @@
1
- """The pulse journal: append-only feed of skill-management events.
2
-
3
- Powers `boost pulse`, `boost trending`, `boost stats`, and `boost who`.
4
- """
5
- from __future__ import annotations
6
-
7
- import json
8
- from typing import List, Optional
9
-
10
- from . import paths, util
11
-
12
- ROTATE_AT = 5000
13
- ROTATE_KEEP = 2500
14
-
15
-
16
- def log(action: str, subject: str = "", **fields) -> None:
17
- """Append one event line to the pulse feed, rotating when oversized.
18
-
19
- None-valued keyword fields are dropped from the record.
20
- """
21
- paths.ensure_dirs()
22
- event = {"ts": util.now_iso(), "user": util.user(), "action": action,
23
- "subject": subject}
24
- event.update({k: v for k, v in fields.items() if v is not None})
25
- p = paths.pulse_path()
26
- with p.open("a") as f:
27
- f.write(json.dumps(event) + "\n")
28
- _maybe_rotate()
29
-
30
-
31
- def events(n: Optional[int] = None, action: Optional[str] = None,
32
- subject: Optional[str] = None) -> List[dict]:
33
- """Most-recent-first list of journal events."""
34
- p = paths.pulse_path()
35
- if not p.exists():
36
- return []
37
- out = []
38
- for line in p.read_text(encoding="utf-8").splitlines():
39
- try:
40
- e = json.loads(line)
41
- except json.JSONDecodeError:
42
- continue
43
- if action and e.get("action") != action:
44
- continue
45
- if subject and e.get("subject") != subject:
46
- continue
47
- out.append(e)
48
- out.reverse()
49
- return out[:n] if n else out
50
-
51
-
52
- def rotation_healthy() -> bool:
53
- """True when the pulse feed is absent or at most ROTATE_AT lines long."""
54
- p = paths.pulse_path()
55
- if not p.exists():
56
- return True
57
- return sum(1 for _ in p.open()) <= ROTATE_AT
58
-
59
-
60
- def _maybe_rotate() -> None:
61
- p = paths.pulse_path()
62
- try:
63
- lines = p.read_text(encoding="utf-8").splitlines()
64
- except OSError:
65
- return
66
- if len(lines) > ROTATE_AT:
67
- # Atomic swap: a bare write_text here races concurrent log() appends and
68
- # can truncate the feed mid-line if interrupted.
69
- util.atomic_write_text(p, "\n".join(lines[-ROTATE_KEEP:]) + "\n")
@@ -1,22 +0,0 @@
1
- ---
2
- id: journal-rotation-race-and-handle-leak
3
- board: code
4
- section: internals
5
- status: planned
6
- category: Concurrency · Bug
7
- complexity: M
8
- impact: Med
9
- wow: 2
10
- note:
11
- order: 31
12
- owner:
13
- pr:
14
- title: Journal rotation has a lost-update race between concurrent processes
15
- ---
16
- <code>_maybe_rotate</code> reads the whole pulse file and atomically replaces it with a truncated
17
- snapshot — but two concurrent boost processes (explicitly expected per this repo's parallel-loop
18
- model) can both read, then both write their own snapshot, and whichever writes last silently
19
- discards any event the other appended in between. <code>rotation_healthy()</code> also opens the
20
- file with a bare <code>p.open()</code> and never closes it, relying on GC instead of a
21
- <code>with</code> block like every other open in the module. Rotate under a lock (or an
22
- append-only rename scheme), and close the handle explicitly.