chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
"""Tests for the generic `agents` platform and its `skills` alias (#1432).
|
||||
|
||||
`graphify install --platform agents` (and the friendly `--platform skills`
|
||||
alias) installs the skill to the cross-framework Agent-Skills locations: the
|
||||
spec's user-global ``~/.agents/skills`` and project ``./.agents/skills`` — the
|
||||
directories ``npx skills`` and spec-compliant frameworks read.
|
||||
|
||||
The bare ``graphify install`` behaviour (claude/windows only) is unchanged; the
|
||||
named platform is opt-in. The ``graphify agents install`` subcommand is the
|
||||
amp-twin: it also wires AGENTS.md, matching the rendered hooks reference.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
import graphify.__main__ as mainmod
|
||||
|
||||
|
||||
# --- destination map -----------------------------------------------------------
|
||||
|
||||
|
||||
def test_agents_user_destination_is_user_global_dot_agents(tmp_path):
|
||||
"""Global agents skill lands at ~/.agents/skills (the spec's user-global dir),
|
||||
NOT amp's ~/.config/agents/skills."""
|
||||
with patch("graphify.__main__.Path.home", return_value=tmp_path):
|
||||
dst = mainmod._platform_skill_destination("agents", project=False)
|
||||
assert dst == tmp_path / ".agents" / "skills" / "graphify" / "SKILL.md"
|
||||
|
||||
|
||||
def test_agents_project_destination_is_dot_agents(tmp_path):
|
||||
"""Project agents skill lands at ./.agents/skills."""
|
||||
dst = mainmod._platform_skill_destination("agents", project=True, project_dir=tmp_path)
|
||||
assert dst == tmp_path / ".agents" / "skills" / "graphify" / "SKILL.md"
|
||||
|
||||
|
||||
# --- the skills alias ----------------------------------------------------------
|
||||
|
||||
|
||||
def test_skills_alias_resolves_to_agents():
|
||||
assert mainmod._canonical_platform("skills") == "agents"
|
||||
assert mainmod._canonical_platform("agents") == "agents"
|
||||
# A non-aliased platform is returned unchanged.
|
||||
assert mainmod._canonical_platform("amp") == "amp"
|
||||
|
||||
|
||||
# --- end-to-end install / uninstall via the CLI --------------------------------
|
||||
|
||||
|
||||
def _run(tmp_path, argv, home):
|
||||
"""Drive main() with argv, cwd at tmp_path, and Path.home redirected."""
|
||||
old_cwd = Path.cwd()
|
||||
try:
|
||||
os.chdir(tmp_path)
|
||||
with patch.object(sys, "argv", ["graphify", *argv]):
|
||||
with patch("graphify.__main__.Path.home", return_value=home):
|
||||
mainmod.main()
|
||||
finally:
|
||||
os.chdir(old_cwd)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platform_arg", ["agents", "skills"])
|
||||
def test_install_platform_agents_writes_user_global_skill_only(tmp_path, platform_arg):
|
||||
"""`graphify install --platform agents|skills` writes ~/.agents/skills/...
|
||||
SKILL.md (+ references) and nothing else — no AGENTS.md (skill-only, like
|
||||
`--platform amp`)."""
|
||||
home = tmp_path / "home"
|
||||
cwd = tmp_path / "cwd"
|
||||
home.mkdir()
|
||||
cwd.mkdir()
|
||||
|
||||
_run(cwd, ["install", "--platform", platform_arg], home)
|
||||
|
||||
skill = home / ".agents" / "skills" / "graphify" / "SKILL.md"
|
||||
assert skill.exists()
|
||||
assert (skill.parent / ".graphify_version").read_text() == mainmod.__version__
|
||||
assert (skill.parent / "references" / "extraction-spec.md").exists()
|
||||
# Skill-only: the --platform path must not write an AGENTS.md.
|
||||
assert not (cwd / "AGENTS.md").exists()
|
||||
|
||||
|
||||
def test_uninstall_platform_agents_removes_user_global_skill(tmp_path):
|
||||
"""Bare `graphify uninstall` clears the ~/.agents/skills skill the AGENTS.md and
|
||||
amp cleanups never reach."""
|
||||
home = tmp_path / "home"
|
||||
cwd = tmp_path / "cwd"
|
||||
home.mkdir()
|
||||
cwd.mkdir()
|
||||
|
||||
_run(cwd, ["install", "--platform", "agents"], home)
|
||||
skill = home / ".agents" / "skills" / "graphify" / "SKILL.md"
|
||||
assert skill.exists()
|
||||
|
||||
_run(cwd, ["uninstall"], home)
|
||||
assert not skill.exists()
|
||||
# The now-empty skill tree is walked away.
|
||||
assert not (home / ".agents" / "skills").exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platform_arg", ["agents", "skills"])
|
||||
def test_uninstall_platform_flag_global_removes_skill(tmp_path, platform_arg):
|
||||
"""`graphify uninstall --platform agents|skills` (global) clears ~/.agents/skills.
|
||||
|
||||
The global uninstall dispatch ignores the selected platform and always runs
|
||||
uninstall_all; this locks in that the CLI form is accepted and that
|
||||
uninstall_all's `_remove_skill_file("agents")` reaches the skill.
|
||||
"""
|
||||
home = tmp_path / "home"
|
||||
cwd = tmp_path / "cwd"
|
||||
home.mkdir()
|
||||
cwd.mkdir()
|
||||
|
||||
_run(cwd, ["install", "--platform", platform_arg], home)
|
||||
skill = home / ".agents" / "skills" / "graphify" / "SKILL.md"
|
||||
assert skill.exists()
|
||||
|
||||
_run(cwd, ["uninstall", "--platform", platform_arg], home)
|
||||
assert not skill.exists()
|
||||
|
||||
|
||||
def test_project_uninstall_all_removes_agents_skill(tmp_path):
|
||||
"""`graphify uninstall --project` (no platform) removes the agents project skill
|
||||
via the _PLATFORM_CONFIG loop — cleanly, despite agents/amp/antigravity sharing
|
||||
the ./.agents/skills path (the loop hits an already-removed tree harmlessly)."""
|
||||
home = tmp_path / "home"
|
||||
proj = tmp_path / "proj"
|
||||
home.mkdir()
|
||||
proj.mkdir()
|
||||
|
||||
_run(proj, ["install", "--project", "--platform", "agents"], home)
|
||||
project_skill = proj / ".agents" / "skills" / "graphify" / "SKILL.md"
|
||||
assert project_skill.exists()
|
||||
|
||||
_run(proj, ["uninstall", "--project"], home)
|
||||
assert not project_skill.exists()
|
||||
|
||||
|
||||
def test_install_platform_agents_project_writes_dot_agents(tmp_path):
|
||||
"""`graphify install --project --platform agents` writes ./.agents/skills and
|
||||
leaves user scope untouched."""
|
||||
home = tmp_path / "home"
|
||||
proj = tmp_path / "proj"
|
||||
home.mkdir()
|
||||
proj.mkdir()
|
||||
|
||||
_run(proj, ["install", "--project", "--platform", "agents"], home)
|
||||
|
||||
project_skill = proj / ".agents" / "skills" / "graphify" / "SKILL.md"
|
||||
assert project_skill.exists()
|
||||
assert (project_skill.parent / "references" / "extraction-spec.md").exists()
|
||||
# User scope was not touched.
|
||||
assert not (home / ".agents" / "skills").exists()
|
||||
|
||||
_run(proj, ["uninstall", "--project", "--platform", "agents"], home)
|
||||
assert not project_skill.exists()
|
||||
|
||||
|
||||
# --- the amp-twin subcommand (graphify agents install) -------------------------
|
||||
|
||||
|
||||
def test_agents_subcommand_install_also_wires_agents_md(tmp_path):
|
||||
"""`graphify agents install` is the amp-twin: skill at ~/.agents/skills PLUS a
|
||||
`## graphify` section in AGENTS.md (so the rendered hooks reference, which
|
||||
points at `graphify agents install`, stays honest)."""
|
||||
home = tmp_path / "home"
|
||||
cwd = tmp_path / "cwd"
|
||||
home.mkdir()
|
||||
cwd.mkdir()
|
||||
|
||||
_run(cwd, ["agents", "install"], home)
|
||||
|
||||
skill = home / ".agents" / "skills" / "graphify" / "SKILL.md"
|
||||
agents_md = cwd / "AGENTS.md"
|
||||
assert skill.exists()
|
||||
assert agents_md.exists()
|
||||
assert "## graphify" in agents_md.read_text(encoding="utf-8")
|
||||
|
||||
_run(cwd, ["agents", "uninstall"], home)
|
||||
assert not skill.exists()
|
||||
# The section is stripped unconditionally: the file is either removed (it held
|
||||
# only our section) or no longer contains the marker.
|
||||
assert not agents_md.exists() or "## graphify" not in agents_md.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_agents_subcommand_install_is_idempotent(tmp_path):
|
||||
"""Running `graphify agents install` twice leaves a single AGENTS.md section."""
|
||||
home = tmp_path / "home"
|
||||
cwd = tmp_path / "cwd"
|
||||
home.mkdir()
|
||||
cwd.mkdir()
|
||||
|
||||
_run(cwd, ["agents", "install"], home)
|
||||
_run(cwd, ["agents", "install"], home)
|
||||
|
||||
body = (cwd / "AGENTS.md").read_text(encoding="utf-8")
|
||||
assert body.count("## graphify") == 1, "AGENTS.md gained a duplicate graphify section"
|
||||
|
||||
|
||||
def test_skills_subcommand_is_the_agents_subcommand(tmp_path):
|
||||
"""`graphify skills install`/`uninstall` behaves exactly like the agents form:
|
||||
skill at ~/.agents/skills (with references) PLUS the AGENTS.md section."""
|
||||
home = tmp_path / "home"
|
||||
cwd = tmp_path / "cwd"
|
||||
home.mkdir()
|
||||
cwd.mkdir()
|
||||
|
||||
_run(cwd, ["skills", "install"], home)
|
||||
skill = home / ".agents" / "skills" / "graphify" / "SKILL.md"
|
||||
agents_md = cwd / "AGENTS.md"
|
||||
assert skill.exists()
|
||||
assert (skill.parent / "references" / "extraction-spec.md").exists()
|
||||
assert agents_md.exists()
|
||||
assert "## graphify" in agents_md.read_text(encoding="utf-8")
|
||||
|
||||
# The `skills` alias of the uninstall subcommand tears it back down.
|
||||
_run(cwd, ["skills", "uninstall"], home)
|
||||
assert not skill.exists()
|
||||
assert not agents_md.exists() or "## graphify" not in agents_md.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
# --- bare install is unchanged -------------------------------------------------
|
||||
|
||||
|
||||
def test_bare_install_does_not_touch_dot_agents(tmp_path):
|
||||
"""`graphify install` (no platform) stays single-platform claude/windows and
|
||||
never populates ~/.agents/skills (the #1432 out-of-scope guarantee)."""
|
||||
home = tmp_path / "home"
|
||||
cwd = tmp_path / "cwd"
|
||||
home.mkdir()
|
||||
cwd.mkdir()
|
||||
|
||||
_run(cwd, ["install"], home)
|
||||
assert not (home / ".agents" / "skills").exists()
|
||||
Reference in New Issue
Block a user