mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
feat(skills_sync): surface collision with reset-hint
When a newly-bundled skill's name collides with a pre-existing user
skill, sync silently kept the user's copy. Users never learned that
a bundled version shipped by that name.
Now (on non-quiet sync only) print:
⚠ <name>: bundled version shipped but you already have a local
skill by this name — yours was kept. Run `hermes skills reset
<name>` to replace it with the bundled version.
No behavior change to manifest writes or to the kept user copy —
purely additive warning on the existing collision-skip path.
This commit is contained in:
parent
3a97fb3d47
commit
24e8a6e701
2 changed files with 29 additions and 0 deletions
|
|
@ -460,6 +460,28 @@ class TestSyncSkills:
|
||||||
"as 'user-modified' — the manifest was poisoned on the first sync."
|
"as 'user-modified' — the manifest was poisoned on the first sync."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_collision_prints_reset_hint(self, tmp_path, capsys):
|
||||||
|
"""Non-quiet sync must print a reset hint when a collision is skipped.
|
||||||
|
|
||||||
|
Silent skip hides the fact that a bundled skill shipped but was
|
||||||
|
shadowed by the user's local copy. The hint tells the user the
|
||||||
|
exact command to take the bundled version instead.
|
||||||
|
"""
|
||||||
|
bundled = self._setup_bundled(tmp_path)
|
||||||
|
skills_dir = tmp_path / "user_skills"
|
||||||
|
manifest_file = skills_dir / ".bundled_manifest"
|
||||||
|
|
||||||
|
user_skill = skills_dir / "category" / "new-skill"
|
||||||
|
user_skill.mkdir(parents=True)
|
||||||
|
(user_skill / "SKILL.md").write_text("# From hub — unrelated to bundled")
|
||||||
|
|
||||||
|
with self._patches(bundled, skills_dir, manifest_file):
|
||||||
|
sync_skills(quiet=False)
|
||||||
|
|
||||||
|
captured = capsys.readouterr().out
|
||||||
|
assert "new-skill" in captured
|
||||||
|
assert "hermes skills reset new-skill" in captured
|
||||||
|
|
||||||
def test_nonexistent_bundled_dir(self, tmp_path):
|
def test_nonexistent_bundled_dir(self, tmp_path):
|
||||||
with patch("tools.skills_sync._get_bundled_dir", return_value=tmp_path / "nope"):
|
with patch("tools.skills_sync._get_bundled_dir", return_value=tmp_path / "nope"):
|
||||||
result = sync_skills(quiet=True)
|
result = sync_skills(quiet=True)
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,13 @@ def sync_skills(quiet: bool = False) -> dict:
|
||||||
skipped += 1
|
skipped += 1
|
||||||
if _dir_hash(dest) == bundled_hash:
|
if _dir_hash(dest) == bundled_hash:
|
||||||
manifest[skill_name] = bundled_hash
|
manifest[skill_name] = bundled_hash
|
||||||
|
elif not quiet:
|
||||||
|
print(
|
||||||
|
f" ⚠ {skill_name}: bundled version shipped but you "
|
||||||
|
f"already have a local skill by this name — yours "
|
||||||
|
f"was kept. Run `hermes skills reset {skill_name}` "
|
||||||
|
f"to replace it with the bundled version."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||||
shutil.copytree(skill_src, dest)
|
shutil.copytree(skill_src, dest)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue