boost-skill-cli 1.0.82__tar.gz → 1.0.83__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 (233) hide show
  1. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/PKG-INFO +1 -1
  2. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/_version.py +2 -2
  3. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/commands/pkg.py +6 -6
  4. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/commands/quality.py +15 -13
  5. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/commands/taps.py +15 -10
  6. boost_skill_cli-1.0.83/boost_cli/core/staleness.py +78 -0
  7. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_skill_cli.egg-info/PKG-INFO +1 -1
  8. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_skill_cli.egg-info/SOURCES.txt +2 -0
  9. boost_skill_cli-1.0.83/docs/roadmap/items/consolidate-skill-staleness-drift-logic-into-cor.md +16 -0
  10. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap.html +13 -12
  11. boost_skill_cli-1.0.83/tests/unit/test_staleness.py +102 -0
  12. boost_skill_cli-1.0.82/docs/roadmap/items/consolidate-skill-staleness-drift-logic-into-cor.md +0 -16
  13. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/.gitattributes +0 -0
  14. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/.gitignore +0 -0
  15. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/CLAUDE.md +0 -0
  16. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/CONTRIBUTING.md +0 -0
  17. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/LICENSE +0 -0
  18. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/MANIFEST.in +0 -0
  19. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/Makefile +0 -0
  20. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/README.md +0 -0
  21. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/SECURITY.md +0 -0
  22. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost +0 -0
  23. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/__init__.py +0 -0
  24. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/__main__.py +0 -0
  25. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/cli.py +0 -0
  26. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/cliparse.py +0 -0
  27. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/commands/__init__.py +0 -0
  28. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/commands/bmad.py +0 -0
  29. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/commands/configuration.py +0 -0
  30. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/commands/discovery.py +0 -0
  31. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/commands/hooks.py +0 -0
  32. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/commands/info.py +0 -0
  33. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/commands/intelligence.py +0 -0
  34. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/commands/team.py +0 -0
  35. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/__init__.py +0 -0
  36. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/agents.py +0 -0
  37. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/ai.py +0 -0
  38. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/catalog.py +0 -0
  39. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/claude_settings.py +0 -0
  40. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/config.py +0 -0
  41. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/dense.py +0 -0
  42. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/embed.py +0 -0
  43. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/frontmatter.py +0 -0
  44. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/gitutil.py +0 -0
  45. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/journal.py +0 -0
  46. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/lockfile.py +0 -0
  47. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/logs.py +0 -0
  48. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/mcp.py +0 -0
  49. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/output.py +0 -0
  50. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/paths.py +0 -0
  51. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/policy.py +0 -0
  52. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/rag.py +0 -0
  53. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/registry.py +0 -0
  54. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/store.py +0 -0
  55. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/core/util.py +0 -0
  56. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/data/registries.json +0 -0
  57. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/errors.py +0 -0
  58. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_cli/spin.py +0 -0
  59. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_skill_cli.egg-info/dependency_links.txt +0 -0
  60. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_skill_cli.egg-info/entry_points.txt +0 -0
  61. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_skill_cli.egg-info/requires.txt +0 -0
  62. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/boost_skill_cli.egg-info/top_level.txt +0 -0
  63. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/.claude/settings.local.json +0 -0
  64. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/DEBUGGING.md +0 -0
  65. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/demo.gif +0 -0
  66. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/demo.tape +0 -0
  67. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/design-roadmap.html +0 -0
  68. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/index.html +0 -0
  69. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/mcp-hub.html +0 -0
  70. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/rag-architecture.md +0 -0
  71. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D01.md +0 -0
  72. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D02.md +0 -0
  73. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D03.md +0 -0
  74. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D04.md +0 -0
  75. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D05.md +0 -0
  76. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D06.md +0 -0
  77. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D07.md +0 -0
  78. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D08.md +0 -0
  79. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D09.md +0 -0
  80. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D10.md +0 -0
  81. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D11.md +0 -0
  82. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D12.md +0 -0
  83. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D13.md +0 -0
  84. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D14.md +0 -0
  85. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D15.md +0 -0
  86. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D16.md +0 -0
  87. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D17.md +0 -0
  88. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D18.md +0 -0
  89. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D19.md +0 -0
  90. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D20.md +0 -0
  91. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D21.md +0 -0
  92. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D22.md +0 -0
  93. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D23.md +0 -0
  94. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D24.md +0 -0
  95. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D25.md +0 -0
  96. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/BOOST-D26.md +0 -0
  97. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/accessibility-audit-pa11y-ci-axe-core.md +0 -0
  98. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/atomic-corruption-safe-lock-file-writes.md +0 -0
  99. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/atomic-skill-install-temp-dir-swap.md +0 -0
  100. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/autonomous-ship-workflow-and-isolated-worktree.md +0 -0
  101. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/bring-commands-under-mutation-testing.md +0 -0
  102. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/broken-link-and-anchor-checking-lychee.md +0 -0
  103. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/build-provenance-slsa-attestations.md +0 -0
  104. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/cache-the-catalog-entry-set-across-rag-queries.md +0 -0
  105. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/capability-manifest-and-least-privilege-policy.md +0 -0
  106. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/clean-env-install-smoke-pip-and-pipx.md +0 -0
  107. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/command-reference-docs-site.md +0 -0
  108. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/complexity-and-dead-code-radar-xenon-vulture.md +0 -0
  109. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/coverage-dashboard-codecov-free-for-oss.md +0 -0
  110. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/coverage-guided-fuzzing-atheris-oss-fuzz.md +0 -0
  111. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/crash-recorder-error-paths-core-logs-py.md +0 -0
  112. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/dependency-cve-gate-pip-audit.md +0 -0
  113. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/docstring-coverage-interrogate.md +0 -0
  114. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/end-of-options-guard-on-git-commands.md +0 -0
  115. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/extension-free-tests-core-dense-py.md +0 -0
  116. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/extract-mcp-http-servers-out-of-configuration-py.md +0 -0
  117. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/finish-mutation-hardening-across-core.md +0 -0
  118. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/fix-self-update-version-detection-dead-branch.md +0 -0
  119. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/fork-safe-network-proxy-handler.md +0 -0
  120. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/frontmatter-scalar-over-coercion.md +0 -0
  121. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/github-pages-deploy-is-broken-every-push.md +0 -0
  122. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/hash-pinned-reproducible-toolchain-uv-lock.md +0 -0
  123. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/html-validation-html-validate.md +0 -0
  124. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/integrity-verification-boost-verify.md +0 -0
  125. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/invocation-pid-logging-for-crash-correlation.md +0 -0
  126. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/layering-guard-import-linter.md +0 -0
  127. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/lighthouse-ci-on-the-pages-site.md +0 -0
  128. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/localize-the-stored-bm25-snippet.md +0 -0
  129. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/lockfile-enforcement-and-commit-pinning.md +0 -0
  130. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/lowest-version-resolution-uv-resolution-lowest-d.md +0 -0
  131. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/make-the-roadmaps-discoverable.md +0 -0
  132. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/markdown-consistency-markdownlint-cli2.md +0 -0
  133. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/memoize-config-load-in-process.md +0 -0
  134. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/modernization-smells-refurb-pyupgrade.md +0 -0
  135. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/mutation-hardening-core-frontmatter-py.md +0 -0
  136. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/mutation-hardening-core-gitutil-py.md +0 -0
  137. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/mutation-hardening-core-store-py.md +0 -0
  138. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/one-command-every-env-nox.md +0 -0
  139. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/one-shared-atomic-write-helper.md +0 -0
  140. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/package-metadata-validation-twine-check-friends.md +0 -0
  141. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/patch-coverage-gate-diff-cover.md +0 -0
  142. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/performance-regression-gate-pytest-benchmark.md +0 -0
  143. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/post-deploy-smoke-headless-load-check.md +0 -0
  144. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/pre-release-python-canary-3-14t-free-threaded.md +0 -0
  145. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/promote-nav-footer-into-the-shared-style-system.md +0 -0
  146. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/prompt-injection-scanning-of-skill-markdown.md +0 -0
  147. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/property-based-tests-hypothesis-on-the-parsers.md +0 -0
  148. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/prose-and-terminology-linting-vale.md +0 -0
  149. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/prune-ignored-dirs-during-scan-dir-walk.md +0 -0
  150. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/quality-dashboard-sonarcloud-free-for-oss.md +0 -0
  151. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/reconcile-the-theme-drift.md +0 -0
  152. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/refresh-the-marketing-surface.md +0 -0
  153. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/reuse-helpers-kill-minor-dead-work.md +0 -0
  154. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/robust-tag-argument-parsing.md +0 -0
  155. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/runner-egress-monitoring-stepsecurity-harden-run.md +0 -0
  156. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/sbom-aware-scanning-osv-scanner.md +0 -0
  157. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/sbom-on-every-release-cyclonedx-syft.md +0 -0
  158. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/second-type-checker-pyright.md +0 -0
  159. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/secret-and-pii-scanning-of-installed-skills.md +0 -0
  160. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/secret-scanning-gitleaks-push-protection.md +0 -0
  161. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/security-linting-bandit-via-ruff-s-rules.md +0 -0
  162. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/shift-left-gate-pre-commit-pre-commit-ci.md +0 -0
  163. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/single-imperative-rule-extractor.md +0 -0
  164. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/single-tech-stack-prober.md +0 -0
  165. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/split-oversized-command-modules.md +0 -0
  166. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/startup-and-import-time-budget-x-importtime.md +0 -0
  167. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/stop-re-serializing-entry-meta-on-every-search.md +0 -0
  168. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/supply-chain-posture-openssf-scorecard.md +0 -0
  169. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/tap-signing-and-provenance-sigstore-minisign.md +0 -0
  170. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/theme-asset-linting-stylelint-eslint.md +0 -0
  171. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/typo-detection-codespell.md +0 -0
  172. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/typosquat-and-name-confusion-detection.md +0 -0
  173. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/unify-tilde-two-copies-have-a-boundary-bug.md +0 -0
  174. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/untrack-generated-build-noise.md +0 -0
  175. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/update-diff-before-apply.md +0 -0
  176. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/visual-regression-pass-on-the-guide.md +0 -0
  177. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/widen-the-ruff-rule-surface-b-sim-c4-perf-ruf.md +0 -0
  178. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/windows-in-the-ci-matrix.md +0 -0
  179. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/workflow-linting-actionlint.md +0 -0
  180. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/docs/roadmap/items/workflow-sast-zizmor.md +0 -0
  181. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/index.html +0 -0
  182. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/pyproject.toml +0 -0
  183. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/scripts/build_registries.py +0 -0
  184. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/scripts/build_roadmap.py +0 -0
  185. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/scripts/mutation_gate.py +0 -0
  186. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/setup.cfg +0 -0
  187. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/style/README.md +0 -0
  188. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/style/boost.css +0 -0
  189. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/style/boost.js +0 -0
  190. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/style/demo.html +0 -0
  191. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/conftest.py +0 -0
  192. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/functional/test_cli_bmad.py +0 -0
  193. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/functional/test_cli_configuration.py +0 -0
  194. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/functional/test_cli_discovery.py +0 -0
  195. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/functional/test_cli_hooks.py +0 -0
  196. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/functional/test_cli_info.py +0 -0
  197. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/functional/test_cli_intelligence.py +0 -0
  198. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/functional/test_cli_pkg.py +0 -0
  199. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/functional/test_cli_quality.py +0 -0
  200. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/functional/test_cli_taps.py +0 -0
  201. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/functional/test_cli_team.py +0 -0
  202. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/functional/test_everyday_loop.py +0 -0
  203. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/make_fixture.py +0 -0
  204. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/smoke.sh +0 -0
  205. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_agents.py +0 -0
  206. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_ai.py +0 -0
  207. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_catalog.py +0 -0
  208. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_claude_settings.py +0 -0
  209. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_cliparse.py +0 -0
  210. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_config.py +0 -0
  211. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_dense.py +0 -0
  212. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_dense_fallback.py +0 -0
  213. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_embed.py +0 -0
  214. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_errors_and_cli_table.py +0 -0
  215. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_frontmatter.py +0 -0
  216. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_gitutil.py +0 -0
  217. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_journal.py +0 -0
  218. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_lockfile.py +0 -0
  219. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_logs.py +0 -0
  220. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_mcp.py +0 -0
  221. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_mutation_hardening.py +0 -0
  222. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_output.py +0 -0
  223. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_paths.py +0 -0
  224. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_policy.py +0 -0
  225. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_rag.py +0 -0
  226. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_registries_fresh.py +0 -0
  227. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_registry.py +0 -0
  228. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_roadmap_fresh.py +0 -0
  229. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_spin.py +0 -0
  230. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_store.py +0 -0
  231. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_token_parity.py +0 -0
  232. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/tests/unit/test_util.py +0 -0
  233. {boost_skill_cli-1.0.82 → boost_skill_cli-1.0.83}/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.82
3
+ Version: 1.0.83
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.82'
22
- __version_tuple__ = version_tuple = (1, 0, 82)
21
+ __version__ = version = '1.0.83'
22
+ __version_tuple__ = version_tuple = (1, 0, 83)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -16,7 +16,7 @@ from typing import Dict, List, Optional
16
16
 
17
17
  from .. import cliparse
18
18
  from ..core import (agents, catalog, gitutil, journal, lockfile, paths,
19
- registry, store, util)
19
+ registry, staleness, store, util)
20
20
  from ..core import output as out
21
21
  from ..errors import BoostError
22
22
 
@@ -287,16 +287,16 @@ def cmd_update(argv: List[str]) -> int:
287
287
  entry = matches[0]
288
288
  old_v = str(lk.get("version", "0.0.0"))
289
289
  new_v = str(entry.get("version", "0.0.0"))
290
- reason = "version" if util.semver_gt(new_v, old_v) else None
291
- if reason is None:
290
+ head, src_sha = "", None
291
+ if not util.semver_gt(new_v, old_v):
292
292
  head = gitutil.head_commit(registry.get(tapname).path)
293
293
  if head and head != lk.get("commit"):
294
294
  try:
295
- src = store.source_dir_for(entry)
295
+ src_sha = util.sha256_dir(store.source_dir_for(entry))
296
296
  except BoostError:
297
297
  continue
298
- if util.sha256_dir(src) != lk.get("sha256"):
299
- reason = "content"
298
+ reason = staleness.upstream_reason(old_v, new_v, lk.get("commit", ""),
299
+ head, lk.get("sha256", ""), src_sha)
300
300
  if reason is None:
301
301
  continue
302
302
  try:
@@ -18,7 +18,7 @@ from typing import List, Optional, Tuple
18
18
  from .. import cliparse
19
19
  from ..core import (agents, ai, catalog, frontmatter, gitutil, journal,
20
20
  lockfile, logs, output as out, paths, policy, registry,
21
- store, util)
21
+ staleness, store, util)
22
22
  from ..errors import BoostError
23
23
 
24
24
  # --- audit: dangerous-content patterns ------------------------------------
@@ -119,18 +119,20 @@ def _drift_status(name: str, entry: dict) -> str:
119
119
  """'in-sync' | 'local-edits' | 'upstream-moved' | 'source-missing'
120
120
  | 'store-missing' | 'n/a' (local imports with no tap source)."""
121
121
  sdir = store.skill_store_dir(name)
122
- if not sdir.is_dir():
123
- return "store-missing"
124
- if util.sha256_dir(sdir) != entry.get("sha256"):
125
- return "local-edits"
126
- if entry.get("tap") == "local":
127
- return "n/a"
128
- try:
129
- src = store.source_dir_for({"name": name, "tap": entry.get("tap", ""),
130
- "rel_dir": entry.get("source_dir", ".")})
131
- except BoostError:
132
- return "source-missing"
133
- return "in-sync" if util.sha256_dir(src) == entry.get("sha256") else "upstream-moved"
122
+ store_sha = util.sha256_dir(sdir) if sdir.is_dir() else None
123
+ is_local = entry.get("tap") == "local"
124
+ lock_sha = entry.get("sha256", "")
125
+ source_sha = None
126
+ if store_sha is not None and store_sha == lock_sha and not is_local:
127
+ try:
128
+ src = store.source_dir_for(
129
+ {"name": name, "tap": entry.get("tap", ""),
130
+ "rel_dir": entry.get("source_dir", ".")})
131
+ except BoostError:
132
+ source_sha = None
133
+ else:
134
+ source_sha = util.sha256_dir(src)
135
+ return staleness.drift_state(store_sha, lock_sha, is_local, source_sha)
134
136
 
135
137
 
136
138
  _DRIFT_STYLE = {"in-sync": out.GREEN, "local-edits": out.YELLOW,
@@ -5,7 +5,7 @@ import argparse
5
5
  import json
6
6
 
7
7
  from .. import cliparse, spin
8
- from ..core import catalog, config, gitutil, journal, lockfile, paths, registry, store, util
8
+ from ..core import catalog, config, gitutil, journal, lockfile, paths, registry, staleness, store, util
9
9
  from ..core import output as out
10
10
  from ..errors import BoostError
11
11
 
@@ -218,9 +218,8 @@ def cmd_outdated(argv) -> int:
218
218
  latest = str(entry.get("version") or "0.0.0")
219
219
  installed_v = str(lk.get("version") or "0.0.0")
220
220
  stale, latest_disp = False, latest
221
- if util.semver_gt(latest, installed_v):
222
- stale = True
223
- else:
221
+ head, src_sha, src_missing = "", None, False
222
+ if not util.semver_gt(latest, installed_v):
224
223
  if tap_name not in heads:
225
224
  try:
226
225
  tap = registry.get(tap_name)
@@ -231,13 +230,19 @@ def cmd_outdated(argv) -> int:
231
230
  head = heads[tap_name]
232
231
  if head and head != lk.get("commit"):
233
232
  try:
234
- src = store.source_dir_for(entry)
233
+ src_sha = util.sha256_dir(store.source_dir_for(entry))
235
234
  except BoostError:
236
- stale, latest_disp = True, "source missing"
237
- else:
238
- if util.sha256_dir(src) != lk.get("sha256"):
239
- stale = True
240
- latest_disp = "%s (%s)" % (latest, head[:7])
235
+ src_missing = True
236
+ if src_missing:
237
+ stale, latest_disp = True, "source missing"
238
+ else:
239
+ reason = staleness.upstream_reason(
240
+ installed_v, latest, lk.get("commit", ""), head,
241
+ lk.get("sha256", ""), src_sha)
242
+ if reason == staleness.VERSION:
243
+ stale = True
244
+ elif reason == staleness.CONTENT:
245
+ stale, latest_disp = True, "%s (%s)" % (latest, head[:7])
241
246
  if stale:
242
247
  results.append({"name": name, "installed": installed_v,
243
248
  "latest": latest_disp, "tap": tap_name,
@@ -0,0 +1,78 @@
1
+ """Single source of truth for "is an installed skill stale vs its tap?".
2
+
3
+ ``cmd_update`` (pkg.py), ``cmd_outdated`` (taps.py) and ``_drift_status``
4
+ (quality.py) each re-derived this from the same three signals — the catalog
5
+ version, the tap's HEAD commit and the source tree's ``sha256`` — so the logic
6
+ lived in three places that would inevitably drift apart. These two pure
7
+ (I/O-free) functions are now the one decision the commands render.
8
+
9
+ Keeping them free of any filesystem/subprocess access is deliberate: the
10
+ commands gather the raw signals (``head_commit``, ``source_dir_for``,
11
+ ``sha256_dir``) and pass them in, so the *decision* stays trivially testable.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from typing import Optional
16
+
17
+ from . import util
18
+
19
+ # ── upstream_reason: why a tap's copy is newer than the installed one ──────
20
+ VERSION = "version" # the catalog advertises a higher semver
21
+ CONTENT = "content" # the tap HEAD moved AND the source tree's bytes changed
22
+
23
+
24
+ def upstream_reason(installed_version: str, latest_version: str,
25
+ installed_commit: str, tap_head: str,
26
+ installed_sha: str, source_sha: Optional[str]
27
+ ) -> Optional[str]:
28
+ """Why the tap's copy is newer than the installed one, else ``None``.
29
+
30
+ Mirrors the historical order exactly: a higher advertised semver wins
31
+ immediately; otherwise a *moved* tap HEAD **plus** a changed source tree
32
+ counts as content drift. ``source_sha`` is ``None`` when the source tree
33
+ could not be hashed (missing checkout) — treated as "no content signal",
34
+ i.e. not stale on that axis.
35
+ """
36
+ if util.semver_gt(latest_version, installed_version):
37
+ return VERSION
38
+ if tap_head and tap_head != installed_commit:
39
+ if source_sha is not None and source_sha != installed_sha:
40
+ return CONTENT
41
+ return None
42
+
43
+
44
+ # ── drift_state: how an installed skill's store copy relates to lock+tap ───
45
+ IN_SYNC = "in-sync"
46
+ LOCAL_EDITS = "local-edits"
47
+ UPSTREAM_MOVED = "upstream-moved"
48
+ SOURCE_MISSING = "source-missing"
49
+ STORE_MISSING = "store-missing"
50
+ NA = "n/a"
51
+
52
+
53
+ def drift_state(store_sha: Optional[str], lock_sha: str,
54
+ is_local: bool, source_sha: Optional[str]) -> str:
55
+ """Drift of an installed skill's store copy vs its lock entry and tap.
56
+
57
+ Signals (all pre-computed by the caller):
58
+
59
+ * ``store_sha`` — ``sha256`` of the on-disk store dir, or ``None`` if the
60
+ store dir is missing.
61
+ * ``lock_sha`` — the ``sha256`` recorded in the lock entry.
62
+ * ``is_local`` — ``True`` for local imports (no tap source to compare).
63
+ * ``source_sha`` — ``sha256`` of the tap source dir, or ``None`` if it is
64
+ unavailable (only consulted once the store copy matches the lock and the
65
+ skill has a tap).
66
+
67
+ The ordering is significant and matches the historical ``_drift_status``:
68
+ a missing store beats local edits, which beat the local/upstream split.
69
+ """
70
+ if store_sha is None:
71
+ return STORE_MISSING
72
+ if store_sha != lock_sha:
73
+ return LOCAL_EDITS
74
+ if is_local:
75
+ return NA
76
+ if source_sha is None:
77
+ return SOURCE_MISSING
78
+ return IN_SYNC if source_sha == lock_sha else UPSTREAM_MOVED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boost-skill-cli
3
- Version: 1.0.82
3
+ Version: 1.0.83
4
4
  Summary: boost — Homebrew for AI coding skills
5
5
  Author: Jonathan Reyes
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -48,6 +48,7 @@ boost_cli/core/paths.py
48
48
  boost_cli/core/policy.py
49
49
  boost_cli/core/rag.py
50
50
  boost_cli/core/registry.py
51
+ boost_cli/core/staleness.py
51
52
  boost_cli/core/store.py
52
53
  boost_cli/core/util.py
53
54
  boost_cli/data/registries.json
@@ -223,6 +224,7 @@ tests/unit/test_registries_fresh.py
223
224
  tests/unit/test_registry.py
224
225
  tests/unit/test_roadmap_fresh.py
225
226
  tests/unit/test_spin.py
227
+ tests/unit/test_staleness.py
226
228
  tests/unit/test_store.py
227
229
  tests/unit/test_token_parity.py
228
230
  tests/unit/test_util.py
@@ -0,0 +1,16 @@
1
+ ---
2
+ id: consolidate-skill-staleness-drift-logic-into-cor
3
+ board: code
4
+ section: shipped
5
+ status: shipped
6
+ category: Correctness
7
+ complexity: M
8
+ impact: High
9
+ wow: 3
10
+ note: three copies → one core module
11
+ order: 3
12
+ owner: loop/consolidate-staleness
13
+ pr: 91
14
+ title: Consolidate skill-staleness / drift logic into core
15
+ ---
16
+ The "is this skill outdated" decision — <code>semver_gt</code> → commit compare → <code>sha256_dir</code> vs lock — was reimplemented nearly verbatim in <code>cmd_update</code>, <code>cmd_outdated</code> and <code>_drift_status</code> — three copies of core business logic that would drift apart. It now lives in one place: <code>core/staleness.py</code> exposes two <b>pure</b> (I/O-free) decisions — <code>upstream_reason()</code> (the version→commit→content ladder rendered by <code>cmd_update</code>/<code>cmd_outdated</code>) and <code>drift_state()</code> (the store/local-edits/upstream ladder rendered by <code>_drift_status</code>). The commands became thin renderers; behavior is byte-for-byte preserved and the new module is fully unit-tested (every branch pinned, all mutants killed).
@@ -115,7 +115,7 @@
115
115
  <section class="block reveal">
116
116
  <div class="stats">
117
117
  <!-- ROADMAP:stats -->
118
- <div class="stat"><b>9</b><span>Shipped</span></div>
118
+ <div class="stat"><b>10</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
121
  <div class="stat"><b>85</b><span>Loop finds</span></div>
@@ -162,6 +162,18 @@
162
162
  </div>
163
163
  </article>
164
164
 
165
+ <article class="cap rcard">
166
+ <div class="head"><span class="pill shipped">Shipped</span><span class="cat">Correctness</span></div>
167
+ <h3>Consolidate skill-staleness / drift logic into core</h3>
168
+ <p>The "is this skill outdated" decision — <code>semver_gt</code> → commit compare → <code>sha256_dir</code> vs lock — was reimplemented nearly verbatim in <code>cmd_update</code>, <code>cmd_outdated</code> and <code>_drift_status</code> — three copies of core business logic that would drift apart. It now lives in one place: <code>core/staleness.py</code> exposes two <b>pure</b> (I/O-free) decisions — <code>upstream_reason()</code> (the version→commit→content ladder rendered by <code>cmd_update</code>/<code>cmd_outdated</code>) and <code>drift_state()</code> (the store/local-edits/upstream ladder rendered by <code>_drift_status</code>). The commands became thin renderers; behavior is byte-for-byte preserved and the new module is fully unit-tested (every branch pinned, all mutants killed).</p>
169
+ <div class="meta">
170
+ <span class="m">Complexity <b>M</b></span>
171
+ <span class="m hi">Impact <b>High</b></span>
172
+ <span class="m">Wow <b class="wow">★★★</b></span>
173
+ <span class="m">three copies → one core module</span>
174
+ </div>
175
+ </article>
176
+
165
177
  <article class="cap rcard">
166
178
  <div class="head"><span class="pill shipped">Shipped</span><span class="cat">Testing</span></div>
167
179
  <h3>Mutation hardening — <code>core/frontmatter.py</code></h3>
@@ -439,17 +451,6 @@
439
451
  </div>
440
452
  </article>
441
453
 
442
- <article class="cap rcard">
443
- <div class="head"><span class="pill next">Next</span><span class="cat">Correctness</span></div>
444
- <h3>Consolidate skill-staleness / drift logic into core</h3>
445
- <p>The "is this skill outdated" decision — <code>semver_gt</code> → commit compare → <code>sha256_dir</code> vs lock — is reimplemented nearly verbatim in <code>cmd_update</code>, <code>cmd_outdated</code> and <code>_drift_status</code> (<code>pkg.py:277 · taps.py:225 · quality.py:120</code>). Three copies of core business logic that <b>will drift apart</b>. Make it one <code>core</code> function the commands render.</p>
446
- <div class="meta">
447
- <span class="m">Complexity <b>M</b></span>
448
- <span class="m hi">Impact <b>High</b></span>
449
- <span class="m">Wow <b class="wow">★★★</b></span>
450
- </div>
451
- </article>
452
-
453
454
  <article class="cap rcard">
454
455
  <div class="head"><span class="pill shipped">Shipped</span><span class="cat">Performance</span></div>
455
456
  <h3>Memoize <code>config.load()</code> in-process</h3>
@@ -0,0 +1,102 @@
1
+ """Unit tests: boost_cli/core/staleness.py — the single-source staleness/drift
2
+ decision that cmd_update, cmd_outdated and _drift_status now render.
3
+
4
+ Both functions are pure, so every branch (and the ordering between branches) is
5
+ pinned here with assertions specific enough to kill mutants.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from boost_cli.core import staleness
10
+
11
+ SHA_A = "a" * 64
12
+ SHA_B = "b" * 64
13
+
14
+
15
+ class TestUpstreamReason:
16
+ def test_higher_semver_is_version(self):
17
+ assert staleness.upstream_reason(
18
+ "1.0.0", "1.1.0", "c1", "c2", SHA_A, SHA_B) == staleness.VERSION
19
+
20
+ def test_version_constant_value(self):
21
+ # pins the literal so a mutated constant is caught
22
+ assert staleness.VERSION == "version"
23
+
24
+ def test_content_constant_value(self):
25
+ assert staleness.CONTENT == "content"
26
+
27
+ def test_version_wins_even_when_commit_and_content_also_differ(self):
28
+ # semver must be checked FIRST — a higher version reports "version"
29
+ # regardless of the (also-changed) commit/content signals.
30
+ assert staleness.upstream_reason(
31
+ "1.0.0", "2.0.0", "old", "new", SHA_A, SHA_B) == staleness.VERSION
32
+
33
+ def test_same_version_no_commit_move_is_none(self):
34
+ assert staleness.upstream_reason(
35
+ "1.0.0", "1.0.0", "c1", "c1", SHA_A, SHA_B) is None
36
+
37
+ def test_empty_tap_head_is_none(self):
38
+ # no HEAD signal (e.g. tap not cloned) → not stale on the content axis
39
+ assert staleness.upstream_reason(
40
+ "1.0.0", "1.0.0", "c1", "", SHA_A, SHA_B) is None
41
+
42
+ def test_moved_commit_but_source_unhashable_is_none(self):
43
+ # source_sha None means "no content signal" — must NOT report content
44
+ assert staleness.upstream_reason(
45
+ "1.0.0", "1.0.0", "old", "new", SHA_A, None) is None
46
+
47
+ def test_moved_commit_same_content_is_none(self):
48
+ assert staleness.upstream_reason(
49
+ "1.0.0", "1.0.0", "old", "new", SHA_A, SHA_A) is None
50
+
51
+ def test_moved_commit_changed_content_is_content(self):
52
+ assert staleness.upstream_reason(
53
+ "1.0.0", "1.0.0", "old", "new", SHA_A, SHA_B) == staleness.CONTENT
54
+
55
+ def test_lower_advertised_version_not_stale_by_version(self):
56
+ # a downgrade in the catalog is not an upgrade — no commit move → None
57
+ assert staleness.upstream_reason(
58
+ "2.0.0", "1.0.0", "c1", "c1", SHA_A, SHA_B) is None
59
+
60
+
61
+ class TestDriftState:
62
+ def test_store_missing(self):
63
+ assert staleness.drift_state(
64
+ None, SHA_A, False, SHA_A) == staleness.STORE_MISSING
65
+
66
+ def test_store_missing_takes_precedence_over_local(self):
67
+ # a missing store is reported even for a local import
68
+ assert staleness.drift_state(
69
+ None, SHA_A, True, None) == staleness.STORE_MISSING
70
+
71
+ def test_local_edits(self):
72
+ assert staleness.drift_state(
73
+ SHA_B, SHA_A, False, SHA_A) == staleness.LOCAL_EDITS
74
+
75
+ def test_local_edits_take_precedence_over_na(self):
76
+ # edited store beats the local/upstream split even for local imports
77
+ assert staleness.drift_state(
78
+ SHA_B, SHA_A, True, None) == staleness.LOCAL_EDITS
79
+
80
+ def test_local_import_in_sync_is_na(self):
81
+ assert staleness.drift_state(
82
+ SHA_A, SHA_A, True, None) == staleness.NA
83
+
84
+ def test_source_missing(self):
85
+ assert staleness.drift_state(
86
+ SHA_A, SHA_A, False, None) == staleness.SOURCE_MISSING
87
+
88
+ def test_in_sync(self):
89
+ assert staleness.drift_state(
90
+ SHA_A, SHA_A, False, SHA_A) == staleness.IN_SYNC
91
+
92
+ def test_upstream_moved(self):
93
+ assert staleness.drift_state(
94
+ SHA_A, SHA_A, False, SHA_B) == staleness.UPSTREAM_MOVED
95
+
96
+ def test_status_constant_values(self):
97
+ assert staleness.IN_SYNC == "in-sync"
98
+ assert staleness.LOCAL_EDITS == "local-edits"
99
+ assert staleness.UPSTREAM_MOVED == "upstream-moved"
100
+ assert staleness.SOURCE_MISSING == "source-missing"
101
+ assert staleness.STORE_MISSING == "store-missing"
102
+ assert staleness.NA == "n/a"
@@ -1,16 +0,0 @@
1
- ---
2
- id: consolidate-skill-staleness-drift-logic-into-cor
3
- board: code
4
- section: internals
5
- status: next
6
- category: Correctness
7
- complexity: M
8
- impact: High
9
- wow: 3
10
- note:
11
- order: 3
12
- owner:
13
- pr:
14
- title: Consolidate skill-staleness / drift logic into core
15
- ---
16
- The "is this skill outdated" decision — <code>semver_gt</code> → commit compare → <code>sha256_dir</code> vs lock — is reimplemented nearly verbatim in <code>cmd_update</code>, <code>cmd_outdated</code> and <code>_drift_status</code> (<code>pkg.py:277 · taps.py:225 · quality.py:120</code>). Three copies of core business logic that <b>will drift apart</b>. Make it one <code>core</code> function the commands render.
File without changes