boost-skill-cli 1.0.56__tar.gz → 1.0.58__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.56 → boost_skill_cli-1.0.58}/PKG-INFO +1 -1
  2. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/_version.py +2 -2
  3. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/store.py +34 -4
  4. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_skill_cli.egg-info/PKG-INFO +1 -1
  5. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_store.py +66 -0
  6. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/.gitignore +0 -0
  7. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/CLAUDE.md +0 -0
  8. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/CONTRIBUTING.md +0 -0
  9. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/LICENSE +0 -0
  10. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/MANIFEST.in +0 -0
  11. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/Makefile +0 -0
  12. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/README.md +0 -0
  13. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/SECURITY.md +0 -0
  14. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost +0 -0
  15. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/__init__.py +0 -0
  16. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/__main__.py +0 -0
  17. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/cli.py +0 -0
  18. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/cliparse.py +0 -0
  19. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/commands/__init__.py +0 -0
  20. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/commands/bmad.py +0 -0
  21. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/commands/configuration.py +0 -0
  22. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/commands/discovery.py +0 -0
  23. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/commands/hooks.py +0 -0
  24. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/commands/info.py +0 -0
  25. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/commands/intelligence.py +0 -0
  26. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/commands/pkg.py +0 -0
  27. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/commands/quality.py +0 -0
  28. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/commands/taps.py +0 -0
  29. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/commands/team.py +0 -0
  30. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/__init__.py +0 -0
  31. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/agents.py +0 -0
  32. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/ai.py +0 -0
  33. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/catalog.py +0 -0
  34. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/claude_settings.py +0 -0
  35. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/config.py +0 -0
  36. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/dense.py +0 -0
  37. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/embed.py +0 -0
  38. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/frontmatter.py +0 -0
  39. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/gitutil.py +0 -0
  40. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/journal.py +0 -0
  41. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/lockfile.py +0 -0
  42. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/logs.py +0 -0
  43. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/mcp.py +0 -0
  44. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/output.py +0 -0
  45. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/paths.py +0 -0
  46. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/policy.py +0 -0
  47. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/rag.py +0 -0
  48. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/registry.py +0 -0
  49. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/core/util.py +0 -0
  50. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/data/registries.json +0 -0
  51. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/errors.py +0 -0
  52. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_cli/spin.py +0 -0
  53. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_skill_cli.egg-info/SOURCES.txt +0 -0
  54. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_skill_cli.egg-info/dependency_links.txt +0 -0
  55. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_skill_cli.egg-info/entry_points.txt +0 -0
  56. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_skill_cli.egg-info/requires.txt +0 -0
  57. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/boost_skill_cli.egg-info/top_level.txt +0 -0
  58. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/docs/.claude/settings.local.json +0 -0
  59. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/docs/DEBUGGING.md +0 -0
  60. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/docs/demo.gif +0 -0
  61. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/docs/demo.tape +0 -0
  62. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/docs/design-roadmap.html +0 -0
  63. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/docs/index.html +0 -0
  64. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/docs/mcp-hub.html +0 -0
  65. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/docs/rag-architecture.md +0 -0
  66. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/docs/roadmap.html +0 -0
  67. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/index.html +0 -0
  68. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/pyproject.toml +0 -0
  69. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/scripts/build_registries.py +0 -0
  70. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/scripts/mutation_gate.py +0 -0
  71. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/setup.cfg +0 -0
  72. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/style/README.md +0 -0
  73. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/style/boost.css +0 -0
  74. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/style/boost.js +0 -0
  75. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/style/demo.html +0 -0
  76. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/conftest.py +0 -0
  77. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/functional/test_cli_bmad.py +0 -0
  78. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/functional/test_cli_configuration.py +0 -0
  79. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/functional/test_cli_discovery.py +0 -0
  80. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/functional/test_cli_hooks.py +0 -0
  81. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/functional/test_cli_info.py +0 -0
  82. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/functional/test_cli_intelligence.py +0 -0
  83. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/functional/test_cli_pkg.py +0 -0
  84. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/functional/test_cli_quality.py +0 -0
  85. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/functional/test_cli_taps.py +0 -0
  86. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/functional/test_cli_team.py +0 -0
  87. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/functional/test_everyday_loop.py +0 -0
  88. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/make_fixture.py +0 -0
  89. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/smoke.sh +0 -0
  90. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_agents.py +0 -0
  91. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_ai.py +0 -0
  92. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_catalog.py +0 -0
  93. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_claude_settings.py +0 -0
  94. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_cliparse.py +0 -0
  95. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_config.py +0 -0
  96. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_dense.py +0 -0
  97. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_dense_fallback.py +0 -0
  98. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_embed.py +0 -0
  99. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_errors_and_cli_table.py +0 -0
  100. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_frontmatter.py +0 -0
  101. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_gitutil.py +0 -0
  102. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_journal.py +0 -0
  103. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_lockfile.py +0 -0
  104. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_logs.py +0 -0
  105. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_mcp.py +0 -0
  106. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_mutation_hardening.py +0 -0
  107. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_output.py +0 -0
  108. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_paths.py +0 -0
  109. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_policy.py +0 -0
  110. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_rag.py +0 -0
  111. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_registry.py +0 -0
  112. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_spin.py +0 -0
  113. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_token_parity.py +0 -0
  114. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/tests/unit/test_util.py +0 -0
  115. {boost_skill_cli-1.0.56 → boost_skill_cli-1.0.58}/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.56
3
+ Version: 1.0.58
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.56'
22
- __version_tuple__ = version_tuple = (1, 0, 56)
21
+ __version__ = version = '1.0.58'
22
+ __version_tuple__ = version_tuple = (1, 0, 58)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -5,8 +5,10 @@ install(): copy skill dir from a tap clone -> store, symlink into every
5
5
  """
6
6
  from __future__ import annotations
7
7
 
8
+ import os
8
9
  import re
9
10
  import shutil
11
+ import tempfile
10
12
  from dataclasses import dataclass, field
11
13
  from pathlib import Path
12
14
  from typing import Dict, List, Optional
@@ -76,10 +78,38 @@ def unlink_agents(name: str) -> List[str]:
76
78
 
77
79
 
78
80
  def _copy_skill(src: Path, dest: Path) -> None:
79
- if dest.exists():
80
- shutil.rmtree(dest)
81
- shutil.copytree(src, dest,
82
- ignore=shutil.ignore_patterns(".git", "__pycache__", ".DS_Store"))
81
+ """Copy a skill tree into ``dest`` atomically.
82
+
83
+ The old rmtree-then-copytree left a window where a crash destroyed the
84
+ previous good copy before the new one finished — on a reinstall/upgrade the
85
+ skill would vanish while the lock file still referenced it, so store and
86
+ lock disagreed. Instead stage the full copy in a temp dir on the same
87
+ filesystem, then swap it in with directory renames: the existing copy stays
88
+ untouched until the new one is complete, the failure window shrinks to two
89
+ fast renames, and a failed swap rolls back to the original.
90
+ """
91
+ dest = Path(dest)
92
+ dest.parent.mkdir(parents=True, exist_ok=True)
93
+ staged = Path(tempfile.mkdtemp(dir=str(dest.parent),
94
+ prefix="." + dest.name + ".tmp"))
95
+ backup = None
96
+ try:
97
+ shutil.copytree(
98
+ src, staged, dirs_exist_ok=True,
99
+ ignore=shutil.ignore_patterns(".git", "__pycache__", ".DS_Store"))
100
+ if dest.exists():
101
+ backup = staged.with_name(staged.name + ".old")
102
+ os.replace(dest, backup) # move the old copy aside (atomic)
103
+ os.replace(staged, dest) # swap the new copy in (atomic)
104
+ except BaseException:
105
+ if backup is not None and not dest.exists():
106
+ os.replace(backup, dest) # swap-in failed: restore the original
107
+ shutil.rmtree(staged, ignore_errors=True)
108
+ if backup is not None and backup.exists():
109
+ shutil.rmtree(backup, ignore_errors=True)
110
+ raise
111
+ if backup is not None:
112
+ shutil.rmtree(backup, ignore_errors=True)
83
113
 
84
114
 
85
115
  def install(entry: dict, force: bool = False,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boost-skill-cli
3
- Version: 1.0.56
3
+ Version: 1.0.58
4
4
  Summary: boost — Homebrew for AI coding skills
5
5
  Author: Jonathan Reyes
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -423,3 +423,69 @@ class TestSyncApply:
423
423
 
424
424
  def test_nothing_to_do_no_actions(self, brainstorming):
425
425
  assert store.sync_apply(store.sync_plan()) == []
426
+
427
+
428
+ class TestCopySkillAtomic:
429
+ def _mkskill(self, root, name, body="v1"):
430
+ d = root / name
431
+ d.mkdir(parents=True)
432
+ (d / "SKILL.md").write_text(body)
433
+ return d
434
+
435
+ def _leftovers(self, parent, keep):
436
+ return sorted(p.name for p in parent.iterdir() if p.name != keep)
437
+
438
+ def test_fresh_copy(self, tmp_path):
439
+ src = self._mkskill(tmp_path / "src", "sk")
440
+ (src / ".git").mkdir()
441
+ (src / ".git" / "cfg").write_text("junk")
442
+ dest = tmp_path / "store" / "sk"
443
+ store._copy_skill(src, dest)
444
+ assert (dest / "SKILL.md").read_text() == "v1"
445
+ assert not (dest / ".git").exists() # ignore patterns honoured
446
+ assert self._leftovers(dest.parent, "sk") == [] # no temp/backup dirs
447
+
448
+ def test_reinstall_replaces_and_cleans_up(self, tmp_path):
449
+ src1 = self._mkskill(tmp_path / "s1", "sk", body="old")
450
+ dest = tmp_path / "store" / "sk"
451
+ store._copy_skill(src1, dest)
452
+ src2 = self._mkskill(tmp_path / "s2", "sk", body="new")
453
+ store._copy_skill(src2, dest)
454
+ assert (dest / "SKILL.md").read_text() == "new"
455
+ assert self._leftovers(dest.parent, "sk") == []
456
+
457
+ def test_swap_in_failure_rolls_back_to_original(self, tmp_path, monkeypatch):
458
+ src1 = self._mkskill(tmp_path / "s1", "sk", body="original")
459
+ dest = tmp_path / "store" / "sk"
460
+ store._copy_skill(src1, dest) # dest now holds the good copy
461
+ src2 = self._mkskill(tmp_path / "s2", "sk", body="broken")
462
+
463
+ real = store.os.replace
464
+ calls = {"n": 0}
465
+
466
+ def flaky(a, b):
467
+ calls["n"] += 1
468
+ if calls["n"] == 2: # the swap-IN of the new copy
469
+ raise OSError("swap failed")
470
+ return real(a, b)
471
+
472
+ monkeypatch.setattr(store.os, "replace", flaky)
473
+ with pytest.raises(OSError, match="swap failed"):
474
+ store._copy_skill(src2, dest)
475
+ # original survives intact, nothing half-swapped, no debris
476
+ assert (dest / "SKILL.md").read_text() == "original"
477
+ assert self._leftovers(dest.parent, "sk") == []
478
+
479
+ def test_copytree_failure_preserves_existing(self, tmp_path, monkeypatch):
480
+ src1 = self._mkskill(tmp_path / "s1", "sk", body="original")
481
+ dest = tmp_path / "store" / "sk"
482
+ store._copy_skill(src1, dest)
483
+
484
+ def boom(*a, **k):
485
+ raise OSError("copy failed")
486
+
487
+ monkeypatch.setattr(store.shutil, "copytree", boom)
488
+ with pytest.raises(OSError, match="copy failed"):
489
+ store._copy_skill(tmp_path / "s1" / "sk", dest)
490
+ assert (dest / "SKILL.md").read_text() == "original"
491
+ assert self._leftovers(dest.parent, "sk") == []
File without changes