Bug fixes: - #52: bundle run-sleep.sh in Claude Code plugin + 4-level fallback - #58: add skillopt-sleep console script entry point in pyproject.toml - #62: filter headless claude -p replay sessions from harvest Plugin sync (Claude Code / Codex / Copilot / OpenClaw): - Document all 22 CLI flags, 7 actions, 4 backends across all SKILL.md files - Document config keys (preferences, gate_mode, dream_rollouts, etc.) - Document memory consolidation (evolve_memory / evolve_skill) - Add schedule/unschedule to all plugins - Copilot MCP: expand schema from 3 → 16 params + schedule tools - OpenClaw: add schedule/unschedule subcommands via shared scheduler Tests: - Cross-plugin parity test (prevents future feature drift) - MCP schema completeness test Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
"""Tests for the Copilot MCP server schema completeness."""
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
# Allow importing from the plugin directory
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "plugins", "copilot"))
|
||||
|
||||
|
||||
class TestMcpSchema(unittest.TestCase):
|
||||
def test_schema_includes_all_engine_flags(self):
|
||||
from mcp_server import _TOOL_SCHEMA
|
||||
required_params = {
|
||||
"project", "backend", "scope", "source", "model",
|
||||
"tasks_file", "target_skill_path", "progress",
|
||||
"max_sessions", "max_tasks", "lookback_hours",
|
||||
"auto_adopt", "json", "edit_budget",
|
||||
}
|
||||
schema_props = set(_TOOL_SCHEMA["properties"].keys())
|
||||
missing = required_params - schema_props
|
||||
self.assertEqual(missing, set(), f"MCP schema missing: {missing}")
|
||||
|
||||
def test_all_backends_in_enum(self):
|
||||
from mcp_server import _TOOL_SCHEMA
|
||||
backends = _TOOL_SCHEMA["properties"]["backend"]["enum"]
|
||||
for b in ["mock", "claude", "codex", "copilot"]:
|
||||
self.assertIn(b, backends)
|
||||
|
||||
def test_schedule_tools_exist(self):
|
||||
from mcp_server import TOOLS
|
||||
names = {t["name"] for t in TOOLS}
|
||||
self.assertIn("sleep_schedule", names)
|
||||
self.assertIn("sleep_unschedule", names)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,87 @@
|
||||
"""Cross-plugin parity tests — ensure all plugins document the same features.
|
||||
|
||||
Run: python3 -m pytest tests/test_plugin_sync.py -v
|
||||
"""
|
||||
import os
|
||||
import unittest
|
||||
|
||||
REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
PLUGIN_SKILL_MDS = {
|
||||
"claude-code": os.path.join(REPO, "plugins/claude-code/skills/skillopt-sleep/SKILL.md"),
|
||||
"codex": os.path.join(REPO, "plugins/codex/skills/skillopt-sleep/SKILL.md"),
|
||||
"openclaw": os.path.join(REPO, "plugins/openclaw/SKILL.md"),
|
||||
}
|
||||
|
||||
MCP_SERVER = os.path.join(REPO, "plugins/copilot/mcp_server.py")
|
||||
COPILOT_INSTRUCTIONS = os.path.join(REPO, "plugins/copilot/copilot-instructions.snippet.md")
|
||||
|
||||
CANONICAL_BACKENDS = {"mock", "claude", "codex", "copilot"}
|
||||
|
||||
|
||||
def _read(path):
|
||||
if not os.path.exists(path):
|
||||
return ""
|
||||
with open(path, encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
class TestPluginParity(unittest.TestCase):
|
||||
def test_all_skill_mds_mention_all_backends(self):
|
||||
for name, path in PLUGIN_SKILL_MDS.items():
|
||||
text = _read(path)
|
||||
if not text:
|
||||
self.skipTest(f"{name} SKILL.md not found")
|
||||
for backend in CANONICAL_BACKENDS:
|
||||
self.assertIn(backend, text,
|
||||
f"{name}/SKILL.md missing backend '{backend}'")
|
||||
|
||||
def test_all_skill_mds_mention_schedule(self):
|
||||
for name, path in PLUGIN_SKILL_MDS.items():
|
||||
text = _read(path)
|
||||
if not text:
|
||||
continue
|
||||
self.assertIn("schedule", text.lower(),
|
||||
f"{name}/SKILL.md missing 'schedule'")
|
||||
self.assertIn("unschedule", text.lower(),
|
||||
f"{name}/SKILL.md missing 'unschedule'")
|
||||
|
||||
def test_copilot_instructions_mention_schedule(self):
|
||||
text = _read(COPILOT_INSTRUCTIONS)
|
||||
self.assertIn("sleep_schedule", text)
|
||||
self.assertIn("sleep_unschedule", text)
|
||||
|
||||
def test_copilot_instructions_mention_all_backends(self):
|
||||
text = _read(COPILOT_INSTRUCTIONS)
|
||||
for backend in CANONICAL_BACKENDS:
|
||||
self.assertIn(backend, text,
|
||||
f"copilot-instructions missing backend '{backend}'")
|
||||
|
||||
def test_mcp_server_has_schedule_tools(self):
|
||||
text = _read(MCP_SERVER)
|
||||
self.assertIn("sleep_schedule", text)
|
||||
self.assertIn("sleep_unschedule", text)
|
||||
|
||||
def test_mcp_schema_has_key_params(self):
|
||||
text = _read(MCP_SERVER)
|
||||
for param in ["source", "tasks_file", "target_skill_path",
|
||||
"max_sessions", "max_tasks", "auto_adopt", "json"]:
|
||||
self.assertIn(f'"{param}"', text,
|
||||
f"MCP schema missing param '{param}'")
|
||||
|
||||
def test_all_skill_mds_mention_memory_consolidation(self):
|
||||
for name, path in PLUGIN_SKILL_MDS.items():
|
||||
text = _read(path).lower()
|
||||
if not text:
|
||||
continue
|
||||
has_mention = (
|
||||
"memory consolidation" in text
|
||||
or "evolve_memory" in text
|
||||
or ("consolidate" in text and "memory" in text)
|
||||
)
|
||||
self.assertTrue(has_mention,
|
||||
f"{name}/SKILL.md missing memory consolidation docs")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user