boost-skill-cli 1.0.52__tar.gz → 1.0.54__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 (115) hide show
  1. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/PKG-INFO +1 -1
  2. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/_version.py +2 -2
  3. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/config.py +3 -2
  4. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/frontmatter.py +8 -3
  5. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/journal.py +3 -1
  6. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/lockfile.py +27 -2
  7. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/util.py +32 -0
  8. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_skill_cli.egg-info/PKG-INFO +1 -1
  9. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_skill_cli.egg-info/SOURCES.txt +2 -0
  10. boost_skill_cli-1.0.54/docs/mcp-hub.html +473 -0
  11. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/docs/roadmap.html +20 -1
  12. boost_skill_cli-1.0.54/tests/unit/test_dense_fallback.py +463 -0
  13. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_frontmatter.py +11 -6
  14. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_lockfile.py +25 -0
  15. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_util.py +44 -0
  16. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/.gitignore +0 -0
  17. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/CLAUDE.md +0 -0
  18. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/CONTRIBUTING.md +0 -0
  19. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/LICENSE +0 -0
  20. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/MANIFEST.in +0 -0
  21. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/Makefile +0 -0
  22. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/README.md +0 -0
  23. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/SECURITY.md +0 -0
  24. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost +0 -0
  25. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/__init__.py +0 -0
  26. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/__main__.py +0 -0
  27. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/cli.py +0 -0
  28. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/cliparse.py +0 -0
  29. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/commands/__init__.py +0 -0
  30. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/commands/bmad.py +0 -0
  31. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/commands/configuration.py +0 -0
  32. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/commands/discovery.py +0 -0
  33. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/commands/hooks.py +0 -0
  34. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/commands/info.py +0 -0
  35. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/commands/intelligence.py +0 -0
  36. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/commands/pkg.py +0 -0
  37. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/commands/quality.py +0 -0
  38. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/commands/taps.py +0 -0
  39. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/commands/team.py +0 -0
  40. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/__init__.py +0 -0
  41. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/agents.py +0 -0
  42. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/ai.py +0 -0
  43. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/catalog.py +0 -0
  44. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/claude_settings.py +0 -0
  45. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/dense.py +0 -0
  46. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/embed.py +0 -0
  47. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/gitutil.py +0 -0
  48. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/logs.py +0 -0
  49. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/mcp.py +0 -0
  50. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/output.py +0 -0
  51. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/paths.py +0 -0
  52. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/policy.py +0 -0
  53. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/rag.py +0 -0
  54. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/registry.py +0 -0
  55. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/core/store.py +0 -0
  56. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/data/registries.json +0 -0
  57. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/errors.py +0 -0
  58. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_cli/spin.py +0 -0
  59. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_skill_cli.egg-info/dependency_links.txt +0 -0
  60. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_skill_cli.egg-info/entry_points.txt +0 -0
  61. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_skill_cli.egg-info/requires.txt +0 -0
  62. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/boost_skill_cli.egg-info/top_level.txt +0 -0
  63. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/docs/.claude/settings.local.json +0 -0
  64. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/docs/DEBUGGING.md +0 -0
  65. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/docs/demo.gif +0 -0
  66. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/docs/demo.tape +0 -0
  67. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/docs/design-roadmap.html +0 -0
  68. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/docs/index.html +0 -0
  69. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/docs/rag-architecture.md +0 -0
  70. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/index.html +0 -0
  71. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/pyproject.toml +0 -0
  72. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/scripts/build_registries.py +0 -0
  73. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/scripts/mutation_gate.py +0 -0
  74. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/setup.cfg +0 -0
  75. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/style/README.md +0 -0
  76. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/style/boost.css +0 -0
  77. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/style/boost.js +0 -0
  78. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/style/demo.html +0 -0
  79. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/conftest.py +0 -0
  80. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/functional/test_cli_bmad.py +0 -0
  81. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/functional/test_cli_configuration.py +0 -0
  82. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/functional/test_cli_discovery.py +0 -0
  83. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/functional/test_cli_hooks.py +0 -0
  84. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/functional/test_cli_info.py +0 -0
  85. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/functional/test_cli_intelligence.py +0 -0
  86. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/functional/test_cli_pkg.py +0 -0
  87. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/functional/test_cli_quality.py +0 -0
  88. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/functional/test_cli_taps.py +0 -0
  89. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/functional/test_cli_team.py +0 -0
  90. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/functional/test_everyday_loop.py +0 -0
  91. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/make_fixture.py +0 -0
  92. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/smoke.sh +0 -0
  93. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_agents.py +0 -0
  94. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_ai.py +0 -0
  95. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_catalog.py +0 -0
  96. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_claude_settings.py +0 -0
  97. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_cliparse.py +0 -0
  98. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_config.py +0 -0
  99. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_dense.py +0 -0
  100. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_embed.py +0 -0
  101. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_errors_and_cli_table.py +0 -0
  102. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_gitutil.py +0 -0
  103. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_journal.py +0 -0
  104. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_logs.py +0 -0
  105. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_mcp.py +0 -0
  106. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_mutation_hardening.py +0 -0
  107. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_output.py +0 -0
  108. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_paths.py +0 -0
  109. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_policy.py +0 -0
  110. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_rag.py +0 -0
  111. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_registry.py +0 -0
  112. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_spin.py +0 -0
  113. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_store.py +0 -0
  114. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/tests/unit/test_token_parity.py +0 -0
  115. {boost_skill_cli-1.0.52 → boost_skill_cli-1.0.54}/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.52
3
+ Version: 1.0.54
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.52'
22
- __version_tuple__ = version_tuple = (1, 0, 52)
21
+ __version__ = version = '1.0.54'
22
+ __version_tuple__ = version_tuple = (1, 0, 54)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -5,7 +5,7 @@ import json
5
5
  from copy import deepcopy
6
6
  from typing import Any
7
7
 
8
- from . import paths
8
+ from . import paths, util
9
9
 
10
10
  DEFAULTS = {
11
11
  "agents": {
@@ -90,7 +90,8 @@ def load() -> dict:
90
90
 
91
91
  def save(cfg: dict) -> None:
92
92
  paths.ensure_dirs()
93
- paths.config_path().write_text(json.dumps(cfg, indent=2, sort_keys=False) + "\n")
93
+ util.atomic_write_text(
94
+ paths.config_path(), json.dumps(cfg, indent=2, sort_keys=False) + "\n")
94
95
 
95
96
 
96
97
  def get(dotted: str, default=None):
@@ -35,12 +35,17 @@ def _scalar(raw: str):
35
35
  return ""
36
36
  if len(s) >= 2 and s[0] == s[-1] and s[0] in "'\"":
37
37
  return s[1:-1]
38
+ # YAML 1.2 core schema only: true/false and null/~. The 1.1 aliases
39
+ # (yes/no/on/off, none) are NOT coerced — they are ordinary English words
40
+ # that legitimately appear as a skill's name or tag ("on", "none"), and
41
+ # silently turning them into bool/None leaks the wrong type into search
42
+ # and ranking meta.
38
43
  low = s.lower()
39
- if low in ("true", "yes", "on"):
44
+ if low == "true":
40
45
  return True
41
- if low in ("false", "no", "off"):
46
+ if low == "false":
42
47
  return False
43
- if low in ("null", "~", "none"):
48
+ if low == "null" or s == "~":
44
49
  return None
45
50
  try:
46
51
  return int(s)
@@ -67,4 +67,6 @@ def _maybe_rotate() -> None:
67
67
  except OSError:
68
68
  return
69
69
  if len(lines) > ROTATE_AT:
70
- p.write_text("\n".join(lines[-ROTATE_KEEP:]) + "\n")
70
+ # Atomic swap: a bare write_text here races concurrent log() appends and
71
+ # can truncate the feed mid-line if interrupted.
72
+ util.atomic_write_text(p, "\n".join(lines[-ROTATE_KEEP:]) + "\n")
@@ -26,16 +26,41 @@ def _skeleton() -> dict:
26
26
  def read() -> dict:
27
27
  p = paths.lockfile_path()
28
28
  if not p.exists():
29
- return _skeleton()
29
+ return _skeleton() # empty: never installed anything yet
30
30
  try:
31
31
  lock = json.loads(p.read_text())
32
32
  except (json.JSONDecodeError, OSError):
33
+ # Corrupt (present but unparseable) is NOT the same as empty: returning
34
+ # a bare skeleton here would let the next write() overwrite the only
35
+ # record of every prior install. Preserve the bytes for recovery and
36
+ # surface it loudly before falling back to the skeleton.
37
+ _preserve_corrupt(p)
33
38
  return _skeleton()
34
39
  lock.setdefault("version", SCHEMA_VERSION)
35
40
  lock.setdefault("skills", {})
36
41
  return lock
37
42
 
38
43
 
44
+ def _preserve_corrupt(p) -> None:
45
+ """Copy an unparseable lock file aside as ``<lock>.corrupt`` and warn.
46
+
47
+ Best effort: a read must still succeed (returning the skeleton) even if the
48
+ sidecar cannot be written, so failures here are swallowed after logging.
49
+ """
50
+ try:
51
+ backup = p.with_name(p.name + ".corrupt")
52
+ shutil.copy(p, backup)
53
+ except OSError:
54
+ backup = None
55
+ try:
56
+ from . import logs
57
+ logs.get_logger().warning(
58
+ "lock file %s is corrupt; preserved %s and continuing with an "
59
+ "empty lock", p, backup or "(backup failed)")
60
+ except Exception:
61
+ pass
62
+
63
+
39
64
  def write(lock: dict) -> None:
40
65
  paths.ensure_dirs()
41
66
  p = paths.lockfile_path()
@@ -52,7 +77,7 @@ def write(lock: dict) -> None:
52
77
  _prune_history()
53
78
  lock["version"] = SCHEMA_VERSION
54
79
  lock["updated"] = util.now_iso()
55
- p.write_text(json.dumps(lock, indent=2, sort_keys=True) + "\n")
80
+ util.atomic_write_text(p, json.dumps(lock, indent=2, sort_keys=True) + "\n")
56
81
 
57
82
 
58
83
  def _history_files() -> List:
@@ -2,7 +2,9 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import hashlib
5
+ import os
5
6
  import re
7
+ import tempfile
6
8
  from datetime import datetime, timezone
7
9
  from pathlib import Path
8
10
  from typing import List, Tuple
@@ -10,6 +12,36 @@ from typing import List, Tuple
10
12
  IGNORED = {".git", "__pycache__", ".DS_Store"}
11
13
 
12
14
 
15
+ def atomic_write_text(path: Path, text: str, encoding: str = "utf-8") -> None:
16
+ """Write ``text`` to ``path`` so a reader never sees a partial file.
17
+
18
+ The bytes go to a temp file in the *same* directory (so the rename stays on
19
+ one filesystem and is atomic), get flushed and fsynced to disk, then swap
20
+ into place with ``os.replace``. An interrupted or concurrent write can no
21
+ longer truncate the target: the previous file survives intact until the
22
+ complete new one atomically replaces it. This is the corruption-safe
23
+ substitute for a bare ``Path.write_text`` on any file boost must not lose —
24
+ the lock file, the config, the pulse journal.
25
+ """
26
+ path = Path(path)
27
+ path.parent.mkdir(parents=True, exist_ok=True)
28
+ fd, tmp = tempfile.mkstemp(dir=str(path.parent),
29
+ prefix="." + path.name + ".", suffix=".tmp")
30
+ try:
31
+ with os.fdopen(fd, "w", encoding=encoding) as f:
32
+ f.write(text)
33
+ f.flush()
34
+ os.fsync(f.fileno())
35
+ os.replace(tmp, str(path))
36
+ except BaseException:
37
+ # Never leave a temp turd behind, and never mask the original error.
38
+ try:
39
+ os.unlink(tmp)
40
+ except OSError:
41
+ pass
42
+ raise
43
+
44
+
13
45
  def now_iso() -> str:
14
46
  return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
15
47
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boost-skill-cli
3
- Version: 1.0.52
3
+ Version: 1.0.54
4
4
  Summary: boost — Homebrew for AI coding skills
5
5
  Author: Jonathan Reyes
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -61,6 +61,7 @@ docs/demo.gif
61
61
  docs/demo.tape
62
62
  docs/design-roadmap.html
63
63
  docs/index.html
64
+ docs/mcp-hub.html
64
65
  docs/rag-architecture.md
65
66
  docs/roadmap.html
66
67
  docs/.claude/settings.local.json
@@ -91,6 +92,7 @@ tests/unit/test_claude_settings.py
91
92
  tests/unit/test_cliparse.py
92
93
  tests/unit/test_config.py
93
94
  tests/unit/test_dense.py
95
+ tests/unit/test_dense_fallback.py
94
96
  tests/unit/test_embed.py
95
97
  tests/unit/test_errors_and_cli_table.py
96
98
  tests/unit/test_frontmatter.py