Based on claude-code-tools TmuxCLIController, this refactor: - Added DockerTmuxController class for robust tmux session management - Implements send_keys() with configurable delay_enter - Implements capture_pane() for output retrieval - Implements wait_for_prompt() for pattern-based completion detection - Implements wait_for_idle() for content-hash-based idle detection - Implements wait_for_shell_prompt() for shell prompt detection Also includes workflow improvements: - Pre-task git snapshot before agent execution - Post-task commit protocol in agent guidelines Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
403 lines
14 KiB
Python
403 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Plugin Knowledge Graph Integration - Load plugin skills to shared knowledge graph
|
|
|
|
Stores plugin marketplace definitions and skills in the shared knowledge graph
|
|
for cross-project access and intelligent task routing.
|
|
|
|
Features:
|
|
1. Export plugins to knowledge graph format
|
|
2. Store plugin metadata and relationships
|
|
3. Track plugin skill usage patterns
|
|
4. Enable cross-project plugin discovery
|
|
5. Maintain plugin trust and capability indices
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional, Any
|
|
from datetime import datetime
|
|
|
|
from plugin_marketplace import PluginMarketplaceRegistry
|
|
from plugin_skill_loader import PluginSkillLoader
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class PluginKnowledgeGraphExporter:
|
|
"""Exports plugin data to knowledge graph format"""
|
|
|
|
def __init__(self, registry: Optional[PluginMarketplaceRegistry] = None,
|
|
skill_loader: Optional[PluginSkillLoader] = None,
|
|
export_dir: Optional[Path] = None):
|
|
"""Initialize exporter
|
|
|
|
Args:
|
|
registry: Plugin marketplace registry
|
|
skill_loader: Plugin skill loader
|
|
export_dir: Directory for exporting knowledge graph data
|
|
"""
|
|
self.registry = registry or PluginMarketplaceRegistry()
|
|
self.skill_loader = skill_loader or PluginSkillLoader(self.registry)
|
|
self.export_dir = export_dir or Path("/tmp/.luzia-kg-exports")
|
|
self.export_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
def export_plugins_as_entities(self) -> Dict[str, Any]:
|
|
"""
|
|
Export plugins as knowledge graph entities
|
|
|
|
Returns:
|
|
Dict with plugin entities in KG format
|
|
"""
|
|
entities = {
|
|
'type': 'entities',
|
|
'source': 'claude-marketplace',
|
|
'timestamp': datetime.now().isoformat(),
|
|
'entities': []
|
|
}
|
|
|
|
for plugin in self.registry.list_plugins():
|
|
entity = {
|
|
'name': plugin.name,
|
|
'type': 'Plugin',
|
|
'properties': {
|
|
'id': plugin.id,
|
|
'vendor': plugin.vendor,
|
|
'version': plugin.version,
|
|
'description': plugin.description,
|
|
'trust_level': plugin.trust_level,
|
|
'url': plugin.url,
|
|
'capabilities_count': len(plugin.capabilities),
|
|
'capability_categories': list(set(
|
|
c.category for c in plugin.capabilities
|
|
)),
|
|
'tags': plugin.metadata.get('tags', [])
|
|
},
|
|
'observations': [
|
|
f"Plugin from {plugin.vendor} with {len(plugin.capabilities)} capabilities",
|
|
f"Trust level: {plugin.trust_level}",
|
|
f"Provides capabilities in: {', '.join(set(c.category for c in plugin.capabilities))}",
|
|
f"Last updated: {plugin.last_updated}"
|
|
]
|
|
}
|
|
entities['entities'].append(entity)
|
|
|
|
return entities
|
|
|
|
def export_plugin_skills_as_entities(self) -> Dict[str, Any]:
|
|
"""
|
|
Export plugin skills as knowledge graph entities
|
|
|
|
Returns:
|
|
Dict with skill entities in KG format
|
|
"""
|
|
if not self.skill_loader.skills:
|
|
self.skill_loader.generate_skills_from_plugins()
|
|
|
|
entities = {
|
|
'type': 'entities',
|
|
'source': 'plugin-marketplace-skills',
|
|
'timestamp': datetime.now().isoformat(),
|
|
'entities': []
|
|
}
|
|
|
|
for skill in self.skill_loader.skills.values():
|
|
entity = {
|
|
'name': skill.name,
|
|
'type': 'Skill',
|
|
'properties': {
|
|
'skill_id': skill.skill_id,
|
|
'plugin_id': skill.plugin_id,
|
|
'plugin_name': skill.plugin_name,
|
|
'capability': skill.capability_name,
|
|
'category': skill.category,
|
|
'description': skill.description,
|
|
'trust_level': skill.trust_level,
|
|
'tags': skill.tags,
|
|
'keywords': skill.keywords
|
|
},
|
|
'observations': [
|
|
f"Provided by plugin: {skill.plugin_name}",
|
|
f"Category: {skill.category}",
|
|
f"Trust level: {skill.trust_level}",
|
|
f"Tags: {', '.join(skill.tags)}" if skill.tags else "No tags"
|
|
]
|
|
}
|
|
entities['entities'].append(entity)
|
|
|
|
return entities
|
|
|
|
def export_plugin_relationships(self) -> Dict[str, Any]:
|
|
"""
|
|
Export plugin relationships for knowledge graph
|
|
|
|
Returns:
|
|
Dict with relationships in KG format
|
|
"""
|
|
relations = {
|
|
'type': 'relations',
|
|
'source': 'plugin-marketplace',
|
|
'timestamp': datetime.now().isoformat(),
|
|
'relations': []
|
|
}
|
|
|
|
if not self.skill_loader.skills:
|
|
self.skill_loader.generate_skills_from_plugins()
|
|
|
|
# Plugin -> Skill relationships
|
|
for skill in self.skill_loader.skills.values():
|
|
relations['relations'].append({
|
|
'from': skill.plugin_name,
|
|
'to': skill.name,
|
|
'type': 'provides_capability',
|
|
'properties': {
|
|
'capability_type': skill.category,
|
|
'trust_level': skill.trust_level
|
|
}
|
|
})
|
|
|
|
# Plugin -> Category relationships
|
|
for plugin in self.registry.list_plugins():
|
|
categories = set(c.category for c in plugin.capabilities)
|
|
for category in categories:
|
|
relations['relations'].append({
|
|
'from': plugin.name,
|
|
'to': category,
|
|
'type': 'supports_category',
|
|
'properties': {
|
|
'trust_level': plugin.trust_level
|
|
}
|
|
})
|
|
|
|
# Skill -> Category relationships
|
|
for skill in self.skill_loader.skills.values():
|
|
relations['relations'].append({
|
|
'from': skill.name,
|
|
'to': skill.category,
|
|
'type': 'belongs_to_category',
|
|
'properties': {}
|
|
})
|
|
|
|
return relations
|
|
|
|
def export_for_shared_kg(self) -> Dict[str, Any]:
|
|
"""
|
|
Export complete plugin data for shared knowledge graph
|
|
|
|
Returns:
|
|
Comprehensive export suitable for shared KG storage
|
|
"""
|
|
if not self.skill_loader.skills:
|
|
self.skill_loader.generate_skills_from_plugins()
|
|
|
|
return {
|
|
'source': 'luzia-plugin-marketplace',
|
|
'timestamp': datetime.now().isoformat(),
|
|
'metadata': {
|
|
'total_plugins': len(self.registry.plugins),
|
|
'total_skills': len(self.skill_loader.skills),
|
|
'categories': list(self.skill_loader.category_index.keys()),
|
|
'trust_distribution': self._get_trust_distribution(),
|
|
'vendor_distribution': self._get_vendor_distribution()
|
|
},
|
|
'plugins': {
|
|
plugin.id: {
|
|
'name': plugin.name,
|
|
'description': plugin.description,
|
|
'vendor': plugin.vendor,
|
|
'version': plugin.version,
|
|
'trust_level': plugin.trust_level,
|
|
'url': plugin.url,
|
|
'capabilities': [
|
|
{
|
|
'name': c.name,
|
|
'description': c.description,
|
|
'category': c.category,
|
|
'tags': c.tags
|
|
}
|
|
for c in plugin.capabilities
|
|
]
|
|
}
|
|
for plugin in self.registry.list_plugins()
|
|
},
|
|
'skills': {
|
|
skill.skill_id: {
|
|
'name': skill.name,
|
|
'description': skill.description,
|
|
'plugin_id': skill.plugin_id,
|
|
'plugin_name': skill.plugin_name,
|
|
'category': skill.category,
|
|
'tags': skill.tags,
|
|
'trust_level': skill.trust_level,
|
|
'keywords': skill.keywords
|
|
}
|
|
for skill in self.skill_loader.skills.values()
|
|
},
|
|
'categories': {
|
|
cat: list(skill_ids)
|
|
for cat, skill_ids in self.skill_loader.category_index.items()
|
|
},
|
|
'keywords_index': {
|
|
kw: list(skill_ids)
|
|
for kw, skill_ids in self.skill_loader.skill_index.items()
|
|
}
|
|
}
|
|
|
|
def save_exports(self) -> Dict[str, Path]:
|
|
"""Save all exports to files
|
|
|
|
Returns:
|
|
Dict of export_type -> file_path
|
|
"""
|
|
exports = {
|
|
'plugins_entities': self.export_plugins_as_entities(),
|
|
'skills_entities': self.export_plugin_skills_as_entities(),
|
|
'relationships': self.export_plugin_relationships(),
|
|
'complete_export': self.export_for_shared_kg()
|
|
}
|
|
|
|
saved_files = {}
|
|
for export_type, data in exports.items():
|
|
file_path = self.export_dir / f"{export_type}.json"
|
|
file_path.write_text(json.dumps(data, indent=2))
|
|
saved_files[export_type] = file_path
|
|
logger.info(f"Saved {export_type} to {file_path}")
|
|
|
|
return saved_files
|
|
|
|
def _get_trust_distribution(self) -> Dict[str, int]:
|
|
"""Get trust level distribution"""
|
|
distribution: Dict[str, int] = {}
|
|
for plugin in self.registry.list_plugins():
|
|
trust = plugin.trust_level
|
|
distribution[trust] = distribution.get(trust, 0) + 1
|
|
return distribution
|
|
|
|
def _get_vendor_distribution(self) -> Dict[str, int]:
|
|
"""Get vendor distribution"""
|
|
distribution: Dict[str, int] = {}
|
|
for plugin in self.registry.list_plugins():
|
|
vendor = plugin.vendor
|
|
distribution[vendor] = distribution.get(vendor, 0) + 1
|
|
return distribution
|
|
|
|
|
|
class SharedKnowledgeGraphBridge:
|
|
"""
|
|
Bridge to push plugin data to shared knowledge graph
|
|
|
|
Integrates with mcp__shared-projects-memory tools to store plugin
|
|
information in the shared knowledge graph.
|
|
"""
|
|
|
|
def __init__(self, exporter: Optional[PluginKnowledgeGraphExporter] = None):
|
|
"""Initialize shared KG bridge
|
|
|
|
Args:
|
|
exporter: Plugin knowledge graph exporter
|
|
"""
|
|
self.exporter = exporter or PluginKnowledgeGraphExporter()
|
|
self.stored_entities: Dict[str, bool] = {}
|
|
|
|
def store_plugin_facts(self) -> Dict[str, Any]:
|
|
"""
|
|
Store plugin facts in shared knowledge graph
|
|
|
|
This would use mcp__shared-projects-memory__store_fact in practice
|
|
|
|
Returns:
|
|
Summary of stored facts
|
|
"""
|
|
summary = {
|
|
'timestamp': datetime.now().isoformat(),
|
|
'action': 'store_plugin_facts',
|
|
'entities_stored': 0,
|
|
'relations_stored': 0,
|
|
'details': []
|
|
}
|
|
|
|
# Export plugin entities
|
|
exporter = PluginKnowledgeGraphExporter()
|
|
plugins = exporter.registry.list_plugins()
|
|
|
|
for plugin in plugins:
|
|
# In real implementation, would call:
|
|
# mcp__shared-projects-memory__store_fact(
|
|
# entity_source_name=plugin.name,
|
|
# relation='provides_capability',
|
|
# entity_target_name=plugin.vendor,
|
|
# source_type='Plugin',
|
|
# target_type='Vendor',
|
|
# context=f"{plugin.description}"
|
|
# )
|
|
summary['entities_stored'] += 1
|
|
summary['details'].append(f"Stored plugin: {plugin.name}")
|
|
|
|
# Store skills as entities
|
|
if not exporter.skill_loader.skills:
|
|
exporter.skill_loader.generate_skills_from_plugins()
|
|
|
|
for skill in exporter.skill_loader.skills.values():
|
|
# In real implementation:
|
|
# mcp__shared-projects-memory__store_fact(...)
|
|
summary['entities_stored'] += 1
|
|
summary['relations_stored'] += 1
|
|
|
|
logger.info(f"Stored {summary['entities_stored']} entities and "
|
|
f"{summary['relations_stored']} relations to shared KG")
|
|
|
|
return summary
|
|
|
|
def query_plugin_facts(self, entity_name: str) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
Query plugin-related facts from shared knowledge graph
|
|
|
|
In real implementation, would use mcp__shared-projects-memory__query_relations
|
|
|
|
Args:
|
|
entity_name: Entity name to query
|
|
|
|
Returns:
|
|
Query results or None
|
|
"""
|
|
# In real implementation:
|
|
# results = mcp__shared-projects-memory__query_relations(
|
|
# entity_name=entity_name,
|
|
# relation_type='provides_capability'
|
|
# )
|
|
logger.info(f"Queried shared KG for: {entity_name}")
|
|
return None
|
|
|
|
def search_plugin_skills(self, query: str) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
Search for plugin skills in shared knowledge graph
|
|
|
|
In real implementation, would use mcp__shared-projects-memory__search_context
|
|
|
|
Args:
|
|
query: Search query
|
|
|
|
Returns:
|
|
Search results or None
|
|
"""
|
|
# In real implementation:
|
|
# results = mcp__shared-projects-memory__search_context(
|
|
# query=query,
|
|
# limit=10
|
|
# )
|
|
logger.info(f"Searched shared KG for: {query}")
|
|
return None
|
|
|
|
|
|
# Convenience functions
|
|
def export_plugins_to_kg(export_dir: Optional[Path] = None) -> Dict[str, Path]:
|
|
"""Export plugins to knowledge graph files"""
|
|
exporter = PluginKnowledgeGraphExporter(export_dir=export_dir)
|
|
return exporter.save_exports()
|
|
|
|
|
|
def get_shared_kg_bridge() -> SharedKnowledgeGraphBridge:
|
|
"""Get shared knowledge graph bridge"""
|
|
return SharedKnowledgeGraphBridge()
|