fixes
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -117,7 +117,8 @@ class MethodologyLoader:
|
||||
if not entry.enabled:
|
||||
raise PluginLoadError(f"Methodology '{name}' is disabled")
|
||||
|
||||
plugin_path = Path(entry.path)
|
||||
# Resolve the plugin path (handles both relative and absolute paths)
|
||||
plugin_path = self._registry.resolve_plugin_path(entry)
|
||||
if not plugin_path.exists():
|
||||
raise PluginLoadError(
|
||||
f"Plugin directory not found for methodology '{name}': {plugin_path}"
|
||||
@@ -409,7 +410,8 @@ class MethodologyLoader:
|
||||
if entry is None:
|
||||
raise PluginLoadError(f"Methodology '{name}' is not registered")
|
||||
|
||||
plugin_path = Path(entry.path)
|
||||
# Resolve the plugin path (handles both relative and absolute paths)
|
||||
plugin_path = self._registry.resolve_plugin_path(entry)
|
||||
return self._load_manifest(name, plugin_path)
|
||||
|
||||
def is_loaded(self, name: str) -> bool:
|
||||
|
||||
@@ -205,9 +205,11 @@ class MethodologyRegistryImpl:
|
||||
if not manifest_data:
|
||||
continue
|
||||
|
||||
# Always store absolute paths to avoid CWD-dependent resolution
|
||||
absolute_path = plugin_dir.resolve()
|
||||
entry = RegistryEntry(
|
||||
name=manifest_data.get("name", plugin_dir.name),
|
||||
path=str(plugin_dir),
|
||||
path=str(absolute_path),
|
||||
version=manifest_data.get("version", "0.0.0"),
|
||||
verified=True,
|
||||
enabled=True,
|
||||
@@ -246,9 +248,11 @@ class MethodologyRegistryImpl:
|
||||
if not manifest_data:
|
||||
continue
|
||||
|
||||
# Always store absolute paths to avoid CWD-dependent resolution
|
||||
absolute_path = plugin_dir.resolve()
|
||||
entry = RegistryEntry(
|
||||
name=manifest_data.get("name", plugin_dir.name),
|
||||
path=str(plugin_dir),
|
||||
path=str(absolute_path),
|
||||
version=manifest_data.get("version", "0.0.0"),
|
||||
verified=False,
|
||||
enabled=True,
|
||||
@@ -345,6 +349,45 @@ class MethodologyRegistryImpl:
|
||||
return entry
|
||||
return None
|
||||
|
||||
def resolve_plugin_path(self, entry: RegistryEntry) -> Path:
|
||||
"""Resolve a plugin path to an absolute path.
|
||||
|
||||
Handles both absolute and relative paths stored in the registry.
|
||||
For verified plugins with relative paths, resolves against verified_plugins_dir.
|
||||
|
||||
Args:
|
||||
entry: Registry entry containing the path
|
||||
|
||||
Returns:
|
||||
Resolved absolute Path to the plugin directory
|
||||
"""
|
||||
plugin_path = Path(entry.path)
|
||||
|
||||
# If absolute and exists, use it directly
|
||||
if plugin_path.is_absolute():
|
||||
return plugin_path
|
||||
|
||||
# For relative paths, try to resolve against verified_plugins_dir
|
||||
if entry.verified and self._verified_plugins_dir is not None:
|
||||
# Check if path is relative to verified_plugins_dir
|
||||
resolved = self._verified_plugins_dir / plugin_path.name
|
||||
if resolved.exists():
|
||||
return resolved
|
||||
|
||||
# Also try joining the full relative path in case it's structured
|
||||
resolved = self._verified_plugins_dir.parent.parent.parent / plugin_path
|
||||
if resolved.exists():
|
||||
return resolved
|
||||
|
||||
# For community plugins, try community_plugins_dir
|
||||
if not entry.verified and self._community_plugins_dir is not None:
|
||||
resolved = self._community_plugins_dir / plugin_path.name
|
||||
if resolved.exists():
|
||||
return resolved
|
||||
|
||||
# Fallback: return as-is (will likely fail, but preserves original behavior)
|
||||
return plugin_path
|
||||
|
||||
def get_methodology(self, name: str) -> MethodologyRunner:
|
||||
"""Get a methodology runner by name.
|
||||
|
||||
|
||||
@@ -79,8 +79,10 @@ if sys.platform == "win32":
|
||||
if "_new_stream" in dir():
|
||||
del _new_stream
|
||||
|
||||
# Add auto-claude to path (parent of runners/)
|
||||
# Add auto-claude to path (parent of runners/) for relative imports
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
# Add project root (parent of apps/) for absolute imports like 'from apps.backend...'
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
|
||||
|
||||
# Validate platform-specific dependencies BEFORE any imports that might
|
||||
# trigger graphiti_core -> real_ladybug -> pywintypes import chain (ACS-253)
|
||||
@@ -246,9 +248,8 @@ def _run_methodology_pipeline(
|
||||
task_config=task_config,
|
||||
)
|
||||
|
||||
# Initialize the runner
|
||||
debug("spec_runner", "Initializing methodology runner")
|
||||
runner.initialize(context)
|
||||
# Note: Runner initialization is handled by FullAutoExecutor.execute()
|
||||
# to avoid double-initialization issues
|
||||
|
||||
# Create and run the executor
|
||||
debug("spec_runner", "Creating FullAutoExecutor")
|
||||
|
||||
@@ -1 +1 @@
|
||||
../../../../../../apps/frontend/node_modules
|
||||
../../node_modules
|
||||
@@ -56,11 +56,6 @@ export const ComplexitySelector = memo(function ComplexitySelector({
|
||||
const { t } = useTranslation(['tasks']);
|
||||
const prefix = idPrefix ? `${idPrefix}-` : '';
|
||||
|
||||
// Find the current option to get its icon and color
|
||||
const currentOption = COMPLEXITY_OPTIONS.find(opt => opt.value === value);
|
||||
const CurrentIcon = currentOption?.icon || Sparkles;
|
||||
const currentColorClass = currentOption?.colorClass || 'text-info';
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<Label
|
||||
@@ -79,10 +74,7 @@ export const ComplexitySelector = memo(function ComplexitySelector({
|
||||
aria-describedby={`${prefix}execution-complexity-help`}
|
||||
className="h-10"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<CurrentIcon className={cn('h-4 w-4', currentColorClass)} />
|
||||
<SelectValue placeholder={t('tasks:executionComplexity.auto.label')} />
|
||||
</div>
|
||||
<SelectValue placeholder={t('tasks:executionComplexity.auto.label')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{COMPLEXITY_OPTIONS.map(({ value: optValue, icon: Icon, colorClass }) => (
|
||||
|
||||
@@ -349,15 +349,19 @@ class TestBMADRunnerInitialization:
|
||||
assert runner._initialized is True
|
||||
assert runner._context is mock_context
|
||||
|
||||
def test_initialize_raises_if_already_initialized(self, mock_context: Any) -> None:
|
||||
"""Test that initializing twice raises RuntimeError."""
|
||||
def test_initialize_allows_reinitialization(self, mock_context: Any) -> None:
|
||||
"""Test that initializing twice resets state for runner reuse."""
|
||||
from apps.backend.methodologies.bmad import BMADRunner
|
||||
|
||||
runner = BMADRunner()
|
||||
runner.initialize(mock_context)
|
||||
|
||||
with pytest.raises(RuntimeError, match="already initialized"):
|
||||
runner.initialize(mock_context)
|
||||
# Should not raise - runners support reuse/reinitialization
|
||||
runner.initialize(mock_context)
|
||||
|
||||
# Runner should still be functional after reinitialization
|
||||
assert runner._initialized is True
|
||||
assert runner._context is mock_context
|
||||
|
||||
def test_get_phases_raises_before_initialization(self) -> None:
|
||||
"""Test that get_phases() raises if not initialized."""
|
||||
|
||||
Reference in New Issue
Block a user