test-devin-plugin.sh 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. #!/usr/bin/env bash
  2. # Validate the Devin CLI integration. `devin plugins install obra/superpowers`
  3. # reads `.devin-plugin/plugin.json` and auto-discovers the co-located `skills/`
  4. # directory; Devin CLI surfaces every installed skill's name + description in
  5. # the system prompt at session start and invokes them via its native `skill`
  6. # tool, and its system prompt already documents its own tools (subagent
  7. # profiles, todo tracking, question prompts), so there is no hook, injector,
  8. # or tool-mapping scaffold to test. What IS Devin-specific is the manifest.
  9. #
  10. # Mirrors tests/kimi/test-plugin-manifest.sh. CI-safe: does not require
  11. # `devin` installed.
  12. set -euo pipefail
  13. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  14. REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
  15. MANIFEST="$REPO_ROOT/.devin-plugin/plugin.json"
  16. fail() { echo "FAIL: $*" >&2; exit 1; }
  17. echo "test-devin-plugin: checking Devin CLI manifest"
  18. # --- Manifest is valid and matches the repo version -------------------------
  19. [ -f "$MANIFEST" ] || fail "manifest missing at $MANIFEST"
  20. python3 - "$MANIFEST" <<'PY'
  21. import json
  22. import sys
  23. from pathlib import Path
  24. manifest_path = Path(sys.argv[1])
  25. manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
  26. repo_root = manifest_path.parents[1]
  27. if manifest.get("name") != "superpowers":
  28. raise AssertionError(f"plugin name: expected 'superpowers', got {manifest.get('name')!r}")
  29. package = json.loads((repo_root / "package.json").read_text(encoding="utf-8"))
  30. if manifest.get("version") != package.get("version"):
  31. raise AssertionError(
  32. f"manifest version {manifest.get('version')!r} != package.json version {package.get('version')!r}"
  33. )
  34. # Devin CLI plugins carry skills only (auto-discovered from ./skills/); the
  35. # manifest supports metadata + dependency lists, nothing executable.
  36. unsupported = ["skills", "hooks", "commands", "sessionStart", "contextFileName", "inject"]
  37. present = sorted(field for field in unsupported if field in manifest)
  38. if present:
  39. raise AssertionError("unsupported Devin manifest fields present: " + ", ".join(present))
  40. version_config = json.loads((repo_root / ".version-bump.json").read_text(encoding="utf-8"))
  41. entries = version_config.get("files")
  42. if not isinstance(entries, list) or not any(
  43. entry.get("path") == ".devin-plugin/plugin.json" and entry.get("field") == "version"
  44. for entry in entries
  45. if isinstance(entry, dict)
  46. ):
  47. raise AssertionError(".version-bump.json must update .devin-plugin/plugin.json version")
  48. print("Devin plugin manifest looks good")
  49. PY
  50. echo "PASS: Devin CLI plugin valid (manifest)"