feat(frontend): add methodology installer and compatibility checking
- Add methodology installer service for downloading and installing methodology packages - Add methodology IPC handlers for frontend-backend communication - Add methodology API module in preload for renderer access - Add useMethodologyCompatibility hook for checking project compatibility - Add MethodologyWarningDialog component for user notifications - Add methodology sources configuration for BMAD and other methodologies - Update AddProjectModal to support methodology selection - Update OnboardingWizard to include methodology setup - Extend useMethodologies hook with installation and verification logic - Add i18n translations for methodology-related UI text - Add methodology types and IPC channel definitions Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
0bd5236a04
commit
c8eaeea6ea
@@ -0,0 +1,171 @@
|
||||
"""Methodology plugin source handlers for multiple distribution types.
|
||||
|
||||
This module defines the source types and verification statuses for methodology plugins,
|
||||
enabling a flexible multi-source plugin distribution system.
|
||||
|
||||
Plugin Classification:
|
||||
- Native: The built-in AutoClaude methodology (not a plugin, always available)
|
||||
- Verified: Plugins maintained and tested by Auto Claude team
|
||||
- Community: Third-party/user-created plugins without verification guarantee
|
||||
|
||||
Distribution Sources:
|
||||
- NATIVE: Built-in, no installation required
|
||||
- NPM: npm/pnpm/yarn packages
|
||||
- GITHUB: GitHub releases or repositories
|
||||
- LOCAL: Manual installation to plugins directory
|
||||
|
||||
Architecture Source: This implements the methodology plugin system design
|
||||
with project-level settings and multi-source versioning.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class SourceType(Enum):
|
||||
"""Distribution source types for methodology plugins."""
|
||||
|
||||
NATIVE = "native" # The built-in AutoClaude methodology (not a plugin)
|
||||
NPM = "npm" # npm/pnpm/yarn packages
|
||||
GITHUB = "github" # GitHub releases or repos
|
||||
LOCAL = "local" # Manual copy to plugins directory
|
||||
|
||||
|
||||
class VerificationStatus(Enum):
|
||||
"""Verification status for methodology plugins."""
|
||||
|
||||
NATIVE = "native" # The AutoClaude methodology itself
|
||||
VERIFIED = "verified" # Maintained and tested by Auto Claude team
|
||||
COMMUNITY = "community" # Third-party, no verification
|
||||
|
||||
|
||||
@dataclass
|
||||
class MethodologySource:
|
||||
"""Configuration for a methodology's distribution source.
|
||||
|
||||
Attributes:
|
||||
type: Distribution source type (native, npm, github, local)
|
||||
verification: Verification status (native, verified, community)
|
||||
package_name: Package identifier (NPM: package name, GitHub: owner/repo)
|
||||
install_command: Override default install command (optional)
|
||||
version_command: Command to check installed version (optional)
|
||||
min_version: Minimum supported version for verified plugins
|
||||
max_version: Maximum supported version (exclusive, optional)
|
||||
"""
|
||||
|
||||
type: SourceType
|
||||
verification: VerificationStatus
|
||||
package_name: str | None = None
|
||||
install_command: str | None = None
|
||||
version_command: str | None = None
|
||||
min_version: str = "1.0.0"
|
||||
max_version: str | None = None
|
||||
|
||||
|
||||
# Registry of known methodologies and their sources
|
||||
# Verified plugins are maintained by Auto Claude and tested for compatibility
|
||||
METHODOLOGY_SOURCES: dict[str, MethodologySource] = {
|
||||
"native": MethodologySource(
|
||||
type=SourceType.NATIVE,
|
||||
verification=VerificationStatus.NATIVE,
|
||||
),
|
||||
"bmad": MethodologySource(
|
||||
type=SourceType.NPM,
|
||||
verification=VerificationStatus.VERIFIED,
|
||||
package_name="bmad-method",
|
||||
install_command="npx bmad-method@alpha install",
|
||||
version_command="npx bmad-method@alpha --version",
|
||||
min_version="1.0.0",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def get_methodology_source(name: str) -> MethodologySource | None:
|
||||
"""Get the source configuration for a methodology.
|
||||
|
||||
Args:
|
||||
name: Name of the methodology (e.g., 'native', 'bmad')
|
||||
|
||||
Returns:
|
||||
MethodologySource if found, None otherwise
|
||||
"""
|
||||
return METHODOLOGY_SOURCES.get(name)
|
||||
|
||||
|
||||
def is_verified_methodology(name: str) -> bool:
|
||||
"""Check if a methodology is verified (maintained by Auto Claude team).
|
||||
|
||||
Args:
|
||||
name: Name of the methodology
|
||||
|
||||
Returns:
|
||||
True if methodology is verified or native, False otherwise
|
||||
"""
|
||||
source = get_methodology_source(name)
|
||||
if source is None:
|
||||
return False
|
||||
return source.verification in (VerificationStatus.NATIVE, VerificationStatus.VERIFIED)
|
||||
|
||||
|
||||
def is_native_methodology(name: str) -> bool:
|
||||
"""Check if a methodology is the native (built-in) methodology.
|
||||
|
||||
Args:
|
||||
name: Name of the methodology
|
||||
|
||||
Returns:
|
||||
True if methodology is native, False otherwise
|
||||
"""
|
||||
source = get_methodology_source(name)
|
||||
if source is None:
|
||||
return False
|
||||
return source.verification == VerificationStatus.NATIVE
|
||||
|
||||
|
||||
def get_install_command(name: str) -> str | None:
|
||||
"""Get the install command for a methodology.
|
||||
|
||||
Args:
|
||||
name: Name of the methodology
|
||||
|
||||
Returns:
|
||||
Install command string if available, None for native or unknown
|
||||
"""
|
||||
source = get_methodology_source(name)
|
||||
if source is None or source.type == SourceType.NATIVE:
|
||||
return None
|
||||
return source.install_command
|
||||
|
||||
|
||||
def get_version_command(name: str) -> str | None:
|
||||
"""Get the version check command for a methodology.
|
||||
|
||||
Args:
|
||||
name: Name of the methodology
|
||||
|
||||
Returns:
|
||||
Version command string if available, None for native or unknown
|
||||
"""
|
||||
source = get_methodology_source(name)
|
||||
if source is None or source.type == SourceType.NATIVE:
|
||||
return None
|
||||
return source.version_command
|
||||
|
||||
|
||||
def list_available_methodologies() -> list[dict]:
|
||||
"""List all registered methodologies with their source info.
|
||||
|
||||
Returns:
|
||||
List of dictionaries with methodology source information
|
||||
"""
|
||||
return [
|
||||
{
|
||||
"name": name,
|
||||
"type": source.type.value,
|
||||
"verification": source.verification.value,
|
||||
"package_name": source.package_name,
|
||||
"min_version": source.min_version,
|
||||
"max_version": source.max_version,
|
||||
}
|
||||
for name, source in METHODOLOGY_SOURCES.items()
|
||||
]
|
||||
@@ -340,10 +340,13 @@ function getInstallCommand(isUpdate: boolean): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape single quotes in a string for use in AppleScript
|
||||
* Escape a string for use in AppleScript double-quoted strings.
|
||||
* AppleScript uses backslash to escape special characters inside double quotes.
|
||||
*/
|
||||
export function escapeAppleScriptString(str: string): string {
|
||||
return str.replace(/'/g, "'\\''");
|
||||
// Escape backslashes first (to avoid double-escaping)
|
||||
// Then escape double quotes
|
||||
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -407,13 +410,15 @@ export async function openTerminalWithCommand(command: string): Promise<void> {
|
||||
|
||||
if (terminalId === 'iterm2') {
|
||||
// iTerm2
|
||||
// IMPORTANT: create window FIRST, then activate - this prevents opening a blank default window
|
||||
// when iTerm2 isn't already running (same pattern as Terminal.app)
|
||||
script = `
|
||||
tell application "iTerm"
|
||||
activate
|
||||
create window with default profile
|
||||
tell current session of current window
|
||||
write text "${escapedCommand}"
|
||||
end tell
|
||||
activate
|
||||
end tell
|
||||
`;
|
||||
} else if (terminalId === 'warp') {
|
||||
|
||||
@@ -34,6 +34,7 @@ import { registerMcpHandlers } from './mcp-handlers';
|
||||
import { registerProfileHandlers } from './profile-handlers';
|
||||
import { registerTerminalWorktreeIpcHandlers } from './terminal';
|
||||
import { registerCheckpointHandlers } from './checkpoint-handlers';
|
||||
import { registerMethodologyHandlers } from './methodology-handlers';
|
||||
import { notificationService } from '../notification-service';
|
||||
|
||||
/**
|
||||
@@ -122,6 +123,9 @@ export function setupIpcHandlers(
|
||||
// Checkpoint handlers (Semi-Auto execution mode - Story 5.4)
|
||||
registerCheckpointHandlers(agentManager, getMainWindow);
|
||||
|
||||
// Methodology plugin handlers (project-level methodology settings)
|
||||
registerMethodologyHandlers();
|
||||
|
||||
console.warn('[IPC] All handler modules registered successfully');
|
||||
}
|
||||
|
||||
@@ -149,5 +153,6 @@ export {
|
||||
registerClaudeCodeHandlers,
|
||||
registerMcpHandlers,
|
||||
registerProfileHandlers,
|
||||
registerCheckpointHandlers
|
||||
registerCheckpointHandlers,
|
||||
registerMethodologyHandlers
|
||||
};
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* IPC handlers for methodology plugin operations.
|
||||
*
|
||||
* Handles methodology checking, installation, configuration,
|
||||
* and version compatibility checks for project-level methodology settings.
|
||||
*/
|
||||
|
||||
import { ipcMain } from 'electron';
|
||||
import { IPC_CHANNELS } from '../../shared/constants';
|
||||
import type { IPCResult } from '../../shared/types';
|
||||
import type {
|
||||
ProjectMethodologyConfig,
|
||||
MethodologyInstallResult,
|
||||
MethodologyCompatibilityResult
|
||||
} from '../../shared/types/methodology';
|
||||
import {
|
||||
checkInstalled,
|
||||
install,
|
||||
getConfig,
|
||||
saveConfig,
|
||||
checkCompatibility,
|
||||
listAvailable
|
||||
} from '../methodology';
|
||||
|
||||
/**
|
||||
* Register all methodology-related IPC handlers.
|
||||
*/
|
||||
export function registerMethodologyHandlers(): void {
|
||||
// Check if methodology is installed in project
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.METHODOLOGY_CHECK_INSTALLED,
|
||||
async (
|
||||
_,
|
||||
projectPath: string,
|
||||
name: string
|
||||
): Promise<IPCResult<MethodologyInstallResult>> => {
|
||||
try {
|
||||
const result = await checkInstalled(projectPath, name);
|
||||
return { success: true, data: result };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to check installation'
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Install methodology
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.METHODOLOGY_INSTALL,
|
||||
async (
|
||||
_,
|
||||
projectPath: string,
|
||||
name: string,
|
||||
version?: string
|
||||
): Promise<IPCResult<MethodologyInstallResult>> => {
|
||||
try {
|
||||
const result = await install(projectPath, name, version);
|
||||
return { success: true, data: result };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to install methodology'
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Get project's methodology config
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.METHODOLOGY_GET_CONFIG,
|
||||
async (
|
||||
_,
|
||||
projectPath: string
|
||||
): Promise<IPCResult<ProjectMethodologyConfig | null>> => {
|
||||
try {
|
||||
const config = await getConfig(projectPath);
|
||||
return { success: true, data: config };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to get config'
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Save project's methodology config
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.METHODOLOGY_SAVE_CONFIG,
|
||||
async (
|
||||
_,
|
||||
projectPath: string,
|
||||
config: ProjectMethodologyConfig
|
||||
): Promise<IPCResult<void>> => {
|
||||
try {
|
||||
await saveConfig(projectPath, config);
|
||||
return { success: true, data: undefined };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to save config'
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// List available methodologies with their sources
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.METHODOLOGY_LIST_AVAILABLE,
|
||||
async (): Promise<IPCResult<ReturnType<typeof listAvailable>>> => {
|
||||
try {
|
||||
const methodologies = listAvailable();
|
||||
return { success: true, data: methodologies };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to list methodologies'
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Check version compatibility
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.METHODOLOGY_CHECK_COMPATIBILITY,
|
||||
async (
|
||||
_,
|
||||
name: string,
|
||||
version: string
|
||||
): Promise<IPCResult<MethodologyCompatibilityResult>> => {
|
||||
try {
|
||||
const result = checkCompatibility(name, version);
|
||||
return { success: true, data: result };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to check compatibility'
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import { setUpdateChannel, setUpdateChannelWithDowngradeCheck } from '../app-upd
|
||||
import { getSettingsPath, readSettingsFile } from '../settings-utils';
|
||||
import { configureTools, getToolPath, getToolInfo, isPathFromWrongPlatform, preWarmToolCache } from '../cli-tool-manager';
|
||||
import { parseEnvFile } from './utils';
|
||||
import { openTerminalWithCommand } from './claude-code-handlers';
|
||||
|
||||
const settingsPath = getSettingsPath();
|
||||
|
||||
@@ -526,6 +527,50 @@ export function registerSettingsHandlers(
|
||||
}
|
||||
);
|
||||
|
||||
// Open terminal with a specific command using user's preferred terminal
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.SHELL_OPEN_TERMINAL_WITH_COMMAND,
|
||||
async (_, command: string, cwd?: string): Promise<IPCResult<void>> => {
|
||||
try {
|
||||
// Validate command input
|
||||
if (!command || typeof command !== 'string' || command.trim() === '') {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Command is required and must be a non-empty string'
|
||||
};
|
||||
}
|
||||
|
||||
// Build the full command with cd if cwd is provided
|
||||
let fullCommand = command;
|
||||
if (cwd) {
|
||||
const resolvedCwd = path.resolve(cwd);
|
||||
if (!existsSync(resolvedCwd)) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Directory does not exist: ${resolvedCwd}`
|
||||
};
|
||||
}
|
||||
// Prefix with cd command
|
||||
fullCommand = process.platform === 'win32'
|
||||
? `cd /d "${resolvedCwd}" && ${command}`
|
||||
: `cd "${resolvedCwd}" && ${command}`;
|
||||
}
|
||||
|
||||
console.log('[Shell] Opening terminal with command:', fullCommand);
|
||||
await openTerminalWithCommand(fullCommand);
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
||||
console.error('[Shell] Failed to open terminal with command:', errorMsg);
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to open terminal with command: ${errorMsg}`
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Auto-Build Source Environment Operations
|
||||
// ============================================
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Methodology plugin management module.
|
||||
*
|
||||
* Exports all methodology-related functionality for project-level
|
||||
* methodology configuration and installation.
|
||||
*/
|
||||
|
||||
export * from './installer';
|
||||
@@ -0,0 +1,344 @@
|
||||
/**
|
||||
* Methodology plugin installation manager.
|
||||
*
|
||||
* Handles checking installation status, installing methodologies from various sources,
|
||||
* and verifying version compatibility.
|
||||
*/
|
||||
|
||||
import { exec } from 'child_process';
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import { promisify } from 'util';
|
||||
import { logger } from '../app-logger';
|
||||
import {
|
||||
METHODOLOGY_SOURCES,
|
||||
getMethodologySource,
|
||||
isNativeMethodology,
|
||||
type MethodologySource
|
||||
} from '../../shared/constants/methodology-sources';
|
||||
import type {
|
||||
ProjectMethodologyConfig,
|
||||
MethodologyInstallResult,
|
||||
MethodologyCompatibilityResult
|
||||
} from '../../shared/types/methodology';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
/** File name for project methodology configuration */
|
||||
const METHODOLOGY_CONFIG_FILE = 'methodology.json';
|
||||
|
||||
/**
|
||||
* Get the path to the methodology config file for a project.
|
||||
*/
|
||||
function getConfigPath(projectPath: string): string {
|
||||
return path.join(projectPath, '.auto-claude', METHODOLOGY_CONFIG_FILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a methodology is installed in a project.
|
||||
*
|
||||
* For native methodology, always returns installed.
|
||||
* For npm methodologies, checks if the install artifacts exist.
|
||||
*/
|
||||
export async function checkInstalled(
|
||||
projectPath: string,
|
||||
name: string
|
||||
): Promise<MethodologyInstallResult> {
|
||||
try {
|
||||
// Native methodology is always available
|
||||
if (isNativeMethodology(name)) {
|
||||
return { success: true, version: '1.0.0' };
|
||||
}
|
||||
|
||||
const source = getMethodologySource(name);
|
||||
if (!source) {
|
||||
return { success: false, error: `Unknown methodology: ${name}` };
|
||||
}
|
||||
|
||||
// Check if methodology config exists for this project
|
||||
const configPath = getConfigPath(projectPath);
|
||||
try {
|
||||
const configContent = await fs.readFile(configPath, 'utf-8');
|
||||
const config: ProjectMethodologyConfig = JSON.parse(configContent);
|
||||
|
||||
if (config.name === name && config.version) {
|
||||
return { success: true, version: config.version };
|
||||
}
|
||||
} catch {
|
||||
// Config doesn't exist or is invalid - methodology not installed
|
||||
}
|
||||
|
||||
// For npm methodologies, try to get version from command
|
||||
if (source.type === 'npm' && source.versionCommand) {
|
||||
try {
|
||||
const { stdout } = await execAsync(source.versionCommand, {
|
||||
cwd: projectPath,
|
||||
timeout: 30000
|
||||
});
|
||||
const version = stdout.trim();
|
||||
if (version) {
|
||||
return { success: true, version };
|
||||
}
|
||||
} catch {
|
||||
// Version command failed - not installed
|
||||
}
|
||||
}
|
||||
|
||||
return { success: false, error: 'Methodology not installed' };
|
||||
} catch (error) {
|
||||
logger.error('Error checking methodology installation:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a methodology in a project.
|
||||
*
|
||||
* For npm methodologies, runs the install command.
|
||||
* For local methodologies, verifies the path exists.
|
||||
*/
|
||||
export async function install(
|
||||
projectPath: string,
|
||||
name: string,
|
||||
version?: string
|
||||
): Promise<MethodologyInstallResult> {
|
||||
try {
|
||||
// Native methodology doesn't need installation
|
||||
if (isNativeMethodology(name)) {
|
||||
// Just save the config
|
||||
await saveConfig(projectPath, {
|
||||
name: 'native',
|
||||
version: '1.0.0',
|
||||
source: 'native',
|
||||
installedAt: new Date().toISOString()
|
||||
});
|
||||
return { success: true, version: '1.0.0' };
|
||||
}
|
||||
|
||||
const source = getMethodologySource(name);
|
||||
if (!source) {
|
||||
return { success: false, error: `Unknown methodology: ${name}` };
|
||||
}
|
||||
|
||||
// Handle npm installation
|
||||
if (source.type === 'npm' && source.installCommand) {
|
||||
logger.info(`Installing methodology ${name} via npm...`);
|
||||
|
||||
// Modify install command if version specified
|
||||
let command = source.installCommand;
|
||||
if (version && source.packageName) {
|
||||
// Replace @alpha or @version with specific version
|
||||
command = command.replace(
|
||||
new RegExp(`${source.packageName}@\\S+`),
|
||||
`${source.packageName}@${version}`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const { stdout, stderr } = await execAsync(command, {
|
||||
cwd: projectPath,
|
||||
timeout: 120000 // 2 minute timeout for install
|
||||
});
|
||||
|
||||
logger.debug('Install output:', stdout);
|
||||
if (stderr) {
|
||||
logger.debug('Install stderr:', stderr);
|
||||
}
|
||||
|
||||
// Get installed version
|
||||
let installedVersion = version || source.minVersion;
|
||||
if (source.versionCommand) {
|
||||
try {
|
||||
const { stdout: versionOutput } = await execAsync(source.versionCommand, {
|
||||
cwd: projectPath,
|
||||
timeout: 30000
|
||||
});
|
||||
installedVersion = versionOutput.trim() || installedVersion;
|
||||
} catch {
|
||||
// Version check failed, use default
|
||||
}
|
||||
}
|
||||
|
||||
// Save configuration
|
||||
await saveConfig(projectPath, {
|
||||
name,
|
||||
version: installedVersion,
|
||||
source: source.type,
|
||||
installedAt: new Date().toISOString(),
|
||||
packageName: source.packageName
|
||||
});
|
||||
|
||||
return { success: true, version: installedVersion };
|
||||
} catch (error) {
|
||||
logger.error('Install command failed:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Install command failed'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle local installation (verify path exists)
|
||||
if (source.type === 'local') {
|
||||
// For local, we just need to verify the methodology exists
|
||||
// and save the config
|
||||
await saveConfig(projectPath, {
|
||||
name,
|
||||
version: source.minVersion,
|
||||
source: 'local',
|
||||
installedAt: new Date().toISOString()
|
||||
});
|
||||
return { success: true, version: source.minVersion };
|
||||
}
|
||||
|
||||
return { success: false, error: `Unsupported source type: ${source.type}` };
|
||||
} catch (error) {
|
||||
logger.error('Error installing methodology:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the methodology configuration for a project.
|
||||
*/
|
||||
export async function getConfig(
|
||||
projectPath: string
|
||||
): Promise<ProjectMethodologyConfig | null> {
|
||||
try {
|
||||
const configPath = getConfigPath(projectPath);
|
||||
const content = await fs.readFile(configPath, 'utf-8');
|
||||
return JSON.parse(content) as ProjectMethodologyConfig;
|
||||
} catch {
|
||||
// Config doesn't exist
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save methodology configuration for a project.
|
||||
*/
|
||||
export async function saveConfig(
|
||||
projectPath: string,
|
||||
config: ProjectMethodologyConfig
|
||||
): Promise<void> {
|
||||
const configPath = getConfigPath(projectPath);
|
||||
|
||||
// Ensure .auto-claude directory exists
|
||||
const autoClaudeDir = path.dirname(configPath);
|
||||
await fs.mkdir(autoClaudeDir, { recursive: true });
|
||||
|
||||
// Write config
|
||||
await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
||||
logger.info(`Saved methodology config for ${config.name} at ${configPath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check version compatibility for a methodology.
|
||||
*/
|
||||
export function checkCompatibility(
|
||||
name: string,
|
||||
installedVersion: string
|
||||
): MethodologyCompatibilityResult {
|
||||
// Native is always compatible
|
||||
if (isNativeMethodology(name)) {
|
||||
return { compatible: true, installedVersion };
|
||||
}
|
||||
|
||||
const source = getMethodologySource(name);
|
||||
if (!source) {
|
||||
return {
|
||||
compatible: false,
|
||||
warning: `Unknown methodology: ${name}`,
|
||||
installedVersion
|
||||
};
|
||||
}
|
||||
|
||||
// Parse versions for comparison
|
||||
const installed = parseVersion(installedVersion);
|
||||
const min = parseVersion(source.minVersion);
|
||||
const max = source.maxVersion ? parseVersion(source.maxVersion) : null;
|
||||
|
||||
// Check minimum version
|
||||
if (compareVersions(installed, min) < 0) {
|
||||
return {
|
||||
compatible: false,
|
||||
warning: `Installed version ${installedVersion} is below minimum required ${source.minVersion}. Consider updating.`,
|
||||
installedVersion,
|
||||
minVersion: source.minVersion,
|
||||
maxVersion: source.maxVersion
|
||||
};
|
||||
}
|
||||
|
||||
// Check maximum version (exclusive)
|
||||
if (max && compareVersions(installed, max) >= 0) {
|
||||
return {
|
||||
compatible: false,
|
||||
warning: `Installed version ${installedVersion} exceeds maximum supported ${source.maxVersion}. This may cause compatibility issues.`,
|
||||
installedVersion,
|
||||
minVersion: source.minVersion,
|
||||
maxVersion: source.maxVersion
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
compatible: true,
|
||||
installedVersion,
|
||||
minVersion: source.minVersion,
|
||||
maxVersion: source.maxVersion
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* List all available methodologies with their source info.
|
||||
*/
|
||||
export function listAvailable(): Array<{
|
||||
name: string;
|
||||
type: string;
|
||||
verification: string;
|
||||
packageName?: string;
|
||||
minVersion: string;
|
||||
maxVersion?: string;
|
||||
}> {
|
||||
return Object.entries(METHODOLOGY_SOURCES).map(([name, source]) => ({
|
||||
name,
|
||||
type: source.type,
|
||||
verification: source.verification,
|
||||
packageName: source.packageName,
|
||||
minVersion: source.minVersion,
|
||||
maxVersion: source.maxVersion
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a semver version string into components.
|
||||
*/
|
||||
function parseVersion(version: string): { major: number; minor: number; patch: number } {
|
||||
const match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
|
||||
if (!match) {
|
||||
return { major: 0, minor: 0, patch: 0 };
|
||||
}
|
||||
return {
|
||||
major: parseInt(match[1], 10),
|
||||
minor: parseInt(match[2], 10),
|
||||
patch: parseInt(match[3], 10)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two parsed versions.
|
||||
* Returns: negative if a < b, 0 if a === b, positive if a > b
|
||||
*/
|
||||
function compareVersions(
|
||||
a: { major: number; minor: number; patch: number },
|
||||
b: { major: number; minor: number; patch: number }
|
||||
): number {
|
||||
if (a.major !== b.major) return a.major - b.major;
|
||||
if (a.minor !== b.minor) return a.minor - b.minor;
|
||||
return a.patch - b.patch;
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import { ClaudeCodeAPI, createClaudeCodeAPI } from './modules/claude-code-api';
|
||||
import { McpAPI, createMcpAPI } from './modules/mcp-api';
|
||||
import { ProfileAPI, createProfileAPI } from './profile-api';
|
||||
import { CheckpointAPI, createCheckpointAPI } from './modules/checkpoint-api';
|
||||
import { MethodologyAPI, createMethodologyAPI } from './modules/methodology-api';
|
||||
|
||||
export interface ElectronAPI extends
|
||||
ProjectAPI,
|
||||
@@ -29,7 +30,8 @@ export interface ElectronAPI extends
|
||||
DebugAPI,
|
||||
ClaudeCodeAPI,
|
||||
McpAPI,
|
||||
ProfileAPI {
|
||||
ProfileAPI,
|
||||
MethodologyAPI {
|
||||
github: GitHubAPI;
|
||||
checkpoints: CheckpointAPI;
|
||||
}
|
||||
@@ -46,6 +48,7 @@ export const createElectronAPI = (): ElectronAPI => ({
|
||||
...createClaudeCodeAPI(),
|
||||
...createMcpAPI(),
|
||||
...createProfileAPI(),
|
||||
...createMethodologyAPI(),
|
||||
github: createGitHubAPI(),
|
||||
checkpoints: createCheckpointAPI()
|
||||
});
|
||||
@@ -65,7 +68,8 @@ export {
|
||||
createDebugAPI,
|
||||
createClaudeCodeAPI,
|
||||
createMcpAPI,
|
||||
createCheckpointAPI
|
||||
createCheckpointAPI,
|
||||
createMethodologyAPI
|
||||
};
|
||||
|
||||
export type {
|
||||
@@ -84,5 +88,6 @@ export type {
|
||||
DebugAPI,
|
||||
ClaudeCodeAPI,
|
||||
McpAPI,
|
||||
CheckpointAPI
|
||||
CheckpointAPI,
|
||||
MethodologyAPI
|
||||
};
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Methodology Plugin API
|
||||
*
|
||||
* Exposes methodology installation, configuration, and compatibility checking
|
||||
* to the renderer for project-level methodology settings.
|
||||
*/
|
||||
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { IPC_CHANNELS } from '../../../shared/constants/ipc';
|
||||
import type { IPCResult } from '../../../shared/types/common';
|
||||
import type {
|
||||
ProjectMethodologyConfig,
|
||||
MethodologyInstallResult,
|
||||
MethodologyCompatibilityResult
|
||||
} from '../../../shared/types/methodology';
|
||||
|
||||
export interface MethodologyAPI {
|
||||
/** Check if a methodology is installed in a project */
|
||||
checkMethodologyInstalled: (
|
||||
projectPath: string,
|
||||
name: string
|
||||
) => Promise<IPCResult<MethodologyInstallResult>>;
|
||||
|
||||
/** Install a methodology in a project */
|
||||
installMethodology: (
|
||||
projectPath: string,
|
||||
name: string,
|
||||
version?: string
|
||||
) => Promise<IPCResult<MethodologyInstallResult>>;
|
||||
|
||||
/** Get project's methodology configuration */
|
||||
getMethodologyConfig: (
|
||||
projectPath: string
|
||||
) => Promise<IPCResult<ProjectMethodologyConfig | null>>;
|
||||
|
||||
/** Save project's methodology configuration */
|
||||
saveMethodologyConfig: (
|
||||
projectPath: string,
|
||||
config: ProjectMethodologyConfig
|
||||
) => Promise<IPCResult<void>>;
|
||||
|
||||
/** List available methodologies with their sources */
|
||||
listAvailableMethodologies: () => Promise<IPCResult<Array<{
|
||||
name: string;
|
||||
type: string;
|
||||
verification: string;
|
||||
packageName?: string;
|
||||
minVersion: string;
|
||||
maxVersion?: string;
|
||||
}>>>;
|
||||
|
||||
/** Check version compatibility for a methodology */
|
||||
checkMethodologyCompatibility: (
|
||||
name: string,
|
||||
version: string
|
||||
) => Promise<IPCResult<MethodologyCompatibilityResult>>;
|
||||
}
|
||||
|
||||
export function createMethodologyAPI(): MethodologyAPI {
|
||||
return {
|
||||
checkMethodologyInstalled: (projectPath: string, name: string) =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.METHODOLOGY_CHECK_INSTALLED, projectPath, name),
|
||||
|
||||
installMethodology: (projectPath: string, name: string, version?: string) =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.METHODOLOGY_INSTALL, projectPath, name, version),
|
||||
|
||||
getMethodologyConfig: (projectPath: string) =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.METHODOLOGY_GET_CONFIG, projectPath),
|
||||
|
||||
saveMethodologyConfig: (projectPath: string, config: ProjectMethodologyConfig) =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.METHODOLOGY_SAVE_CONFIG, projectPath, config),
|
||||
|
||||
listAvailableMethodologies: () =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.METHODOLOGY_LIST_AVAILABLE),
|
||||
|
||||
checkMethodologyCompatibility: (name: string, version: string) =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.METHODOLOGY_CHECK_COMPATIBILITY, name, version),
|
||||
};
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import type { IPCResult } from '../../../shared/types';
|
||||
export interface ShellAPI {
|
||||
openExternal: (url: string) => Promise<void>;
|
||||
openTerminal: (dirPath: string) => Promise<IPCResult<void>>;
|
||||
/** Open user's preferred terminal with a command. Optionally cd to a directory first. */
|
||||
openTerminalWithCommand: (command: string, cwd?: string) => Promise<IPCResult<void>>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -17,5 +19,7 @@ export const createShellAPI = (): ShellAPI => ({
|
||||
openExternal: (url: string): Promise<void> =>
|
||||
invokeIpc(IPC_CHANNELS.SHELL_OPEN_EXTERNAL, url),
|
||||
openTerminal: (dirPath: string): Promise<IPCResult<void>> =>
|
||||
invokeIpc(IPC_CHANNELS.SHELL_OPEN_TERMINAL, dirPath)
|
||||
invokeIpc(IPC_CHANNELS.SHELL_OPEN_TERMINAL, dirPath),
|
||||
openTerminalWithCommand: (command: string, cwd?: string): Promise<IPCResult<void>> =>
|
||||
invokeIpc(IPC_CHANNELS.SHELL_OPEN_TERMINAL_WITH_COMMAND, command, cwd)
|
||||
});
|
||||
|
||||
@@ -51,6 +51,8 @@ import { OnboardingWizard } from './components/onboarding';
|
||||
import { AppUpdateNotification } from './components/AppUpdateNotification';
|
||||
import { ProactiveSwapListener } from './components/ProactiveSwapListener';
|
||||
import { GitHubSetupModal } from './components/GitHubSetupModal';
|
||||
import { MethodologyWarningDialog } from './components/MethodologyWarningDialog';
|
||||
import { useMethodologyCompatibility } from './hooks/useMethodologyCompatibility';
|
||||
import { useProjectStore, loadProjects, addProject, initializeProject, removeProject } from './stores/project-store';
|
||||
import { useTaskStore, loadTasks } from './stores/task-store';
|
||||
import { useSettingsStore, loadSettings, loadProfiles } from './stores/settings-store';
|
||||
@@ -61,7 +63,7 @@ import { initDownloadProgressListener } from './stores/download-store';
|
||||
import { GlobalDownloadIndicator } from './components/GlobalDownloadIndicator';
|
||||
import { useIpcListeners } from './hooks/useIpc';
|
||||
import { useGlobalTerminalListeners } from './hooks/useGlobalTerminalListeners';
|
||||
import { COLOR_THEMES, UI_SCALE_MIN, UI_SCALE_MAX, UI_SCALE_DEFAULT } from '../shared/constants';
|
||||
import { COLOR_THEMES, UI_SCALE_MIN, UI_SCALE_MAX, UI_SCALE_DEFAULT, getInstallCommand } from '../shared/constants';
|
||||
import type { Task, Project, ColorTheme } from '../shared/types';
|
||||
import { ProjectTabBar } from './components/ProjectTabBar';
|
||||
import { AddProjectModal } from './components/AddProjectModal';
|
||||
@@ -105,6 +107,14 @@ export function App() {
|
||||
// This ensures terminal output is captured even when the terminal component is not rendered
|
||||
useGlobalTerminalListeners();
|
||||
|
||||
// Methodology compatibility check
|
||||
const {
|
||||
warning: methodologyWarning,
|
||||
dismissWarning: dismissMethodologyWarning,
|
||||
continueAnyway: continueWithMethodology,
|
||||
switchToNative: switchToNativeMethodology,
|
||||
} = useMethodologyCompatibility();
|
||||
|
||||
// Stores
|
||||
const projects = useProjectStore((state) => state.projects);
|
||||
const selectedProjectId = useProjectStore((state) => state.selectedProjectId);
|
||||
@@ -665,6 +675,39 @@ export function App() {
|
||||
const updatedProject = useProjectStore.getState().projects.find(p => p.id === projectId);
|
||||
console.log('[InitDialog] Updated project:', updatedProject);
|
||||
|
||||
// Save methodology configuration AFTER initialization completes
|
||||
// This ensures .auto-claude directory exists before we write to it
|
||||
// Note: Use pendingProject for methodology since it was set during project creation
|
||||
// (updatedProject from store may not have settings synced yet)
|
||||
const projectPath = updatedProject?.path || pendingProject?.path;
|
||||
if (projectPath) {
|
||||
const methodology = pendingProject?.settings?.methodology || updatedProject?.settings?.methodology || 'native';
|
||||
console.log('[InitDialog] Saving methodology config:', methodology, 'from pendingProject:', pendingProject?.settings?.methodology);
|
||||
try {
|
||||
await window.electronAPI.saveMethodologyConfig(projectPath, {
|
||||
name: methodology,
|
||||
version: '1.0.0',
|
||||
source: methodology === 'native' ? 'native' : 'npm',
|
||||
installedAt: new Date().toISOString()
|
||||
});
|
||||
|
||||
// If methodology requires installation, open user's preferred terminal with install command
|
||||
const installCommand = getInstallCommand(methodology);
|
||||
if (installCommand) {
|
||||
console.log('[InitDialog] Opening terminal for methodology installation:', methodology, installCommand);
|
||||
try {
|
||||
await window.electronAPI.openTerminalWithCommand(installCommand, projectPath);
|
||||
} catch (termErr) {
|
||||
console.warn('[InitDialog] Failed to open terminal for methodology install:', termErr);
|
||||
// Non-fatal - user can run the command manually
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[InitDialog] Failed to save methodology config:', err);
|
||||
// Non-fatal - methodology can be configured later
|
||||
}
|
||||
}
|
||||
|
||||
// Mark as successful to prevent onOpenChange from treating this as a skip
|
||||
setInitSuccess(true);
|
||||
setIsInitializing(false);
|
||||
@@ -1036,6 +1079,14 @@ export function App() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Methodology Version Warning Dialog - shows when incompatible methodology version detected */}
|
||||
<MethodologyWarningDialog
|
||||
warning={methodologyWarning}
|
||||
onContinue={continueWithMethodology}
|
||||
onSwitchToNative={switchToNativeMethodology}
|
||||
onDismiss={dismissMethodologyWarning}
|
||||
/>
|
||||
|
||||
{/* Remove Project Confirmation Dialog */}
|
||||
<Dialog open={showRemoveProjectDialog} onOpenChange={(open) => {
|
||||
if (!open) handleCancelRemoveProject();
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from './ui/dialog';
|
||||
import { cn } from '../lib/utils';
|
||||
import { addProject } from '../stores/project-store';
|
||||
import { MethodologySelector } from './task-form/MethodologySelector';
|
||||
import type { Project } from '../../shared/types';
|
||||
|
||||
type ModalStep = 'choose' | 'create-form';
|
||||
@@ -30,6 +31,7 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje
|
||||
const [projectName, setProjectName] = useState('');
|
||||
const [projectLocation, setProjectLocation] = useState('');
|
||||
const [initGit, setInitGit] = useState(true);
|
||||
const [methodology, setMethodology] = useState('native');
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
@@ -40,6 +42,7 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje
|
||||
setProjectName('');
|
||||
setProjectLocation('');
|
||||
setInitGit(true);
|
||||
setMethodology('native');
|
||||
setError(null);
|
||||
}
|
||||
}, [open]);
|
||||
@@ -139,7 +142,26 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje
|
||||
// Non-fatal - main branch can be set later in settings
|
||||
}
|
||||
}
|
||||
onProjectAdded?.(project, true); // New projects always need init
|
||||
|
||||
// Save methodology preference in project settings (NOT methodology.json yet)
|
||||
// The methodology.json will be saved AFTER initialization completes
|
||||
// to avoid creating .auto-claude directory prematurely
|
||||
let projectWithSettings = project;
|
||||
try {
|
||||
await window.electronAPI.updateProjectSettings(project.id, {
|
||||
methodology
|
||||
});
|
||||
// Also update local project object so it's available in pendingProject
|
||||
// The IPC call updates the main store, but we need it in the object we pass
|
||||
projectWithSettings = {
|
||||
...project,
|
||||
settings: { ...project.settings, methodology }
|
||||
};
|
||||
} catch {
|
||||
// Non-fatal - methodology can be set later
|
||||
}
|
||||
|
||||
onProjectAdded?.(projectWithSettings, true); // New projects always need init
|
||||
onOpenChange(false);
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -273,6 +295,13 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
{/* Methodology Selector */}
|
||||
<MethodologySelector
|
||||
value={methodology}
|
||||
onChange={setMethodology}
|
||||
idPrefix="create-project"
|
||||
/>
|
||||
|
||||
{error && (
|
||||
<div className="text-sm text-destructive bg-destructive/10 rounded-lg p-3" role="alert">
|
||||
{error}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* MethodologyWarningDialog - Displays version compatibility warnings
|
||||
*
|
||||
* Shows when a methodology's installed version may not be compatible
|
||||
* with the current Auto Claude version. Provides options to:
|
||||
* - Continue anyway (at user's own risk)
|
||||
* - Switch to native methodology
|
||||
*/
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AlertTriangle, Play, Home, RefreshCw } from 'lucide-react';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from './ui/alert-dialog';
|
||||
import { Button } from './ui/button';
|
||||
import type { MethodologyWarningInfo } from '../hooks/useMethodologyCompatibility';
|
||||
|
||||
interface MethodologyWarningDialogProps {
|
||||
/** Warning information to display */
|
||||
warning: MethodologyWarningInfo | null;
|
||||
/** Called when user chooses to continue anyway */
|
||||
onContinue: () => void;
|
||||
/** Called when user chooses to switch to native */
|
||||
onSwitchToNative: () => void;
|
||||
/** Called when dialog is dismissed */
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
export function MethodologyWarningDialog({
|
||||
warning,
|
||||
onContinue,
|
||||
onSwitchToNative,
|
||||
onDismiss,
|
||||
}: MethodologyWarningDialogProps) {
|
||||
const { t } = useTranslation(['tasks', 'common']);
|
||||
|
||||
if (!warning?.showWarning) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AlertDialog open={warning.showWarning} onOpenChange={(open) => !open && onDismiss()}>
|
||||
<AlertDialogContent className="max-w-md">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="flex items-center gap-2 text-warning">
|
||||
<AlertTriangle className="h-5 w-5" />
|
||||
{t('tasks:methodology.versionWarning.title')}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="space-y-4">
|
||||
<p>
|
||||
{t('tasks:methodology.versionWarning.description', {
|
||||
methodology: warning.methodologyName.toUpperCase(),
|
||||
version: warning.installedVersion,
|
||||
minVersion: warning.minVersion || '1.0.0',
|
||||
})}
|
||||
</p>
|
||||
{warning.warningMessage && (
|
||||
<p className="text-sm text-muted-foreground bg-muted p-3 rounded-md">
|
||||
{warning.warningMessage}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('tasks:methodology.versionWarning.riskNotice')}
|
||||
</p>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter className="flex-col sm:flex-row gap-2">
|
||||
<Button variant="outline" onClick={onSwitchToNative} className="gap-2">
|
||||
<Home className="h-4 w-4" />
|
||||
{t('tasks:methodology.versionWarning.useNative')}
|
||||
</Button>
|
||||
<AlertDialogCancel onClick={onDismiss}>
|
||||
{t('common:cancel')}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={onContinue} className="gap-2">
|
||||
<Play className="h-4 w-4" />
|
||||
{t('tasks:methodology.versionWarning.continueAnyway')}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import { ClaudeCodeStep } from './ClaudeCodeStep';
|
||||
import { DevToolsStep } from './DevToolsStep';
|
||||
import { PrivacyStep } from './PrivacyStep';
|
||||
import { GraphitiStep } from './GraphitiStep';
|
||||
import { MethodologyStep } from './MethodologyStep';
|
||||
// Note: MethodologyStep removed from onboarding - methodology is now a per-project setting
|
||||
import { CompletionStep } from './CompletionStep';
|
||||
import { useSettingsStore } from '../../stores/settings-store';
|
||||
|
||||
@@ -30,7 +30,8 @@ interface OnboardingWizardProps {
|
||||
}
|
||||
|
||||
// Wizard step identifiers
|
||||
type WizardStepId = 'welcome' | 'auth-choice' | 'oauth' | 'claude-code' | 'devtools' | 'privacy' | 'graphiti' | 'methodology' | 'completion';
|
||||
// Note: 'methodology' step removed - methodology is now configured at project level
|
||||
type WizardStepId = 'welcome' | 'auth-choice' | 'oauth' | 'claude-code' | 'devtools' | 'privacy' | 'graphiti' | 'completion';
|
||||
|
||||
// Step configuration with translation keys
|
||||
const WIZARD_STEPS: { id: WizardStepId; labelKey: string }[] = [
|
||||
@@ -41,7 +42,6 @@ const WIZARD_STEPS: { id: WizardStepId; labelKey: string }[] = [
|
||||
{ id: 'devtools', labelKey: 'steps.devtools' },
|
||||
{ id: 'privacy', labelKey: 'steps.privacy' },
|
||||
{ id: 'graphiti', labelKey: 'steps.memory' },
|
||||
{ id: 'methodology', labelKey: 'steps.methodology' },
|
||||
{ id: 'completion', labelKey: 'steps.done' }
|
||||
];
|
||||
|
||||
@@ -231,14 +231,7 @@ export function OnboardingWizard({
|
||||
onSkip={skipWizard}
|
||||
/>
|
||||
);
|
||||
case 'methodology':
|
||||
return (
|
||||
<MethodologyStep
|
||||
onNext={goToNextStep}
|
||||
onBack={goToPreviousStep}
|
||||
onSkip={goToNextStep}
|
||||
/>
|
||||
);
|
||||
// Note: 'methodology' case removed - methodology is now configured at project level
|
||||
case 'completion':
|
||||
return (
|
||||
<CompletionStep
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
/**
|
||||
* Hook for fetching available methodology plugins
|
||||
*
|
||||
* Currently returns static methodology data. Future implementation will
|
||||
* fetch from the methodology registry via IPC (Epic 6).
|
||||
* Fetches methodologies from the registry via IPC and optionally checks
|
||||
* installation status for a specific project.
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import type { MethodologyInfo } from '../../../shared/types/methodology';
|
||||
import type { MethodologyInfo, MethodologyWithStatus } from '../../../shared/types/methodology';
|
||||
import {
|
||||
METHODOLOGY_SOURCES,
|
||||
isVerifiedMethodology,
|
||||
isNativeMethodology
|
||||
} from '../../../shared/constants/methodology-sources';
|
||||
|
||||
/**
|
||||
* Static methodology data for initial implementation.
|
||||
* Will be replaced with IPC call to methodology registry in Epic 6.
|
||||
* Static methodology data as fallback.
|
||||
* Used when IPC calls fail or for initial loading.
|
||||
*/
|
||||
const STATIC_METHODOLOGIES: MethodologyInfo[] = [
|
||||
{
|
||||
@@ -44,6 +49,17 @@ export interface UseMethodologiesResult {
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
export interface UseMethodologiesWithStatusResult {
|
||||
/** List of available methodologies with installation status */
|
||||
methodologies: MethodologyWithStatus[];
|
||||
/** Whether methodologies are currently loading */
|
||||
isLoading: boolean;
|
||||
/** Error message if loading failed */
|
||||
error: string | null;
|
||||
/** Refetch methodologies */
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to fetch available methodology plugins
|
||||
*
|
||||
@@ -59,10 +75,27 @@ export function useMethodologies(): UseMethodologiesResult {
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// Epic 6 will replace this with: await window.electronAPI.getMethodologies()
|
||||
// Try to load from registry via IPC
|
||||
const result = await window.electronAPI.listAvailableMethodologies();
|
||||
if (result.success && result.data) {
|
||||
// Map registry data to MethodologyInfo
|
||||
const methodologyList: MethodologyInfo[] = result.data.map(m => ({
|
||||
name: m.name,
|
||||
version: m.minVersion,
|
||||
description: getMethodologyDescription(m.name),
|
||||
author: getMethodologyAuthor(m.name),
|
||||
complexity_levels: ['quick', 'standard', 'complex'],
|
||||
execution_modes: ['full_auto', 'semi_auto'],
|
||||
is_verified: m.verification === 'verified' || m.verification === 'native',
|
||||
}));
|
||||
setMethodologies(methodologyList);
|
||||
} else {
|
||||
// Fallback to static methodologies
|
||||
setMethodologies(STATIC_METHODOLOGIES);
|
||||
}
|
||||
} catch {
|
||||
// Fallback to static methodologies
|
||||
setMethodologies(STATIC_METHODOLOGIES);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to load methodologies');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -79,3 +112,111 @@ export function useMethodologies(): UseMethodologiesResult {
|
||||
refetch: loadMethodologies,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to fetch methodologies with installation status for a specific project
|
||||
*
|
||||
* @param projectPath - Path to the project to check installation status
|
||||
* @returns Object containing methodologies with status, loading state, and error
|
||||
*/
|
||||
export function useMethodologiesWithStatus(
|
||||
projectPath: string | null
|
||||
): UseMethodologiesWithStatusResult {
|
||||
const [methodologies, setMethodologies] = useState<MethodologyWithStatus[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const loadMethodologies = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// Load available methodologies from registry
|
||||
const result = await window.electronAPI.listAvailableMethodologies();
|
||||
const methodologyList = result.success && result.data ? result.data : [];
|
||||
|
||||
// Build methodology list with installation status
|
||||
const methodologiesWithStatus: MethodologyWithStatus[] = await Promise.all(
|
||||
methodologyList.map(async (m) => {
|
||||
let isInstalled = false;
|
||||
let version = m.minVersion;
|
||||
|
||||
// Check installation status if project path provided
|
||||
if (projectPath) {
|
||||
if (isNativeMethodology(m.name)) {
|
||||
isInstalled = true;
|
||||
} else {
|
||||
const installResult = await window.electronAPI.checkMethodologyInstalled(
|
||||
projectPath,
|
||||
m.name
|
||||
);
|
||||
if (installResult.success && installResult.data?.success) {
|
||||
isInstalled = true;
|
||||
version = installResult.data.version || m.minVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: m.name,
|
||||
version,
|
||||
description: getMethodologyDescription(m.name),
|
||||
author: getMethodologyAuthor(m.name),
|
||||
complexity_levels: ['quick', 'standard', 'complex'],
|
||||
execution_modes: ['full_auto', 'semi_auto'],
|
||||
is_verified: m.verification === 'verified' || m.verification === 'native',
|
||||
isInstalled,
|
||||
sourceType: m.type as 'native' | 'npm' | 'github' | 'local',
|
||||
verification: m.verification as 'native' | 'verified' | 'community',
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
setMethodologies(methodologiesWithStatus);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to load methodologies');
|
||||
// Fallback with basic status
|
||||
setMethodologies(STATIC_METHODOLOGIES.map(m => ({
|
||||
...m,
|
||||
isInstalled: m.name === 'native',
|
||||
sourceType: 'native' as const,
|
||||
verification: 'native' as const,
|
||||
})));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [projectPath]);
|
||||
|
||||
useEffect(() => {
|
||||
loadMethodologies();
|
||||
}, [loadMethodologies]);
|
||||
|
||||
return {
|
||||
methodologies,
|
||||
isLoading,
|
||||
error,
|
||||
refetch: loadMethodologies,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get description for a methodology by name
|
||||
*/
|
||||
function getMethodologyDescription(name: string): string {
|
||||
const descriptions: Record<string, string> = {
|
||||
native: 'Built-in methodology with spec creation and implementation phases',
|
||||
bmad: 'BMAD (Business Model Agile Development) - structured approach with PRD, architecture, and story-driven development',
|
||||
};
|
||||
return descriptions[name] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get author for a methodology by name
|
||||
*/
|
||||
function getMethodologyAuthor(name: string): string {
|
||||
const authors: Record<string, string> = {
|
||||
native: 'Auto Claude',
|
||||
bmad: 'BMad',
|
||||
};
|
||||
return authors[name] || 'Community';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* Hook for checking methodology version compatibility when a project is activated.
|
||||
*
|
||||
* This hook monitors project changes and checks if the installed methodology
|
||||
* version is compatible with Auto Claude requirements. It returns warning
|
||||
* information when incompatibilities are detected.
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useProjectStore } from '../stores/project-store';
|
||||
import type { MethodologyCompatibilityResult, ProjectMethodologyConfig } from '../../shared/types/methodology';
|
||||
import { isNativeMethodology } from '../../shared/constants/methodology-sources';
|
||||
|
||||
export interface MethodologyWarningInfo {
|
||||
/** Whether a warning should be displayed */
|
||||
showWarning: boolean;
|
||||
/** The methodology name */
|
||||
methodologyName: string;
|
||||
/** The installed version */
|
||||
installedVersion: string;
|
||||
/** Warning message from compatibility check */
|
||||
warningMessage: string;
|
||||
/** Minimum required version */
|
||||
minVersion?: string;
|
||||
/** Maximum supported version */
|
||||
maxVersion?: string;
|
||||
}
|
||||
|
||||
export interface UseMethodologyCompatibilityResult {
|
||||
/** Warning information if incompatibility detected */
|
||||
warning: MethodologyWarningInfo | null;
|
||||
/** Whether compatibility check is in progress */
|
||||
isChecking: boolean;
|
||||
/** Dismiss the current warning */
|
||||
dismissWarning: () => void;
|
||||
/** Choose to continue anyway with current version */
|
||||
continueAnyway: () => void;
|
||||
/** Switch to native methodology */
|
||||
switchToNative: () => Promise<void>;
|
||||
/** Recheck compatibility */
|
||||
recheckCompatibility: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to check methodology compatibility for the active project.
|
||||
*
|
||||
* Version checking occurs:
|
||||
* - On project switch (when activeProjectId changes)
|
||||
* - On component mount (app startup)
|
||||
* - When explicitly triggered via recheckCompatibility
|
||||
*
|
||||
* Does NOT check:
|
||||
* - On every task creation (too frequent)
|
||||
*/
|
||||
export function useMethodologyCompatibility(): UseMethodologyCompatibilityResult {
|
||||
const [warning, setWarning] = useState<MethodologyWarningInfo | null>(null);
|
||||
const [isChecking, setIsChecking] = useState(false);
|
||||
const [dismissedForSession, setDismissedForSession] = useState<Set<string>>(new Set());
|
||||
|
||||
const activeProjectId = useProjectStore((state) => state.activeProjectId);
|
||||
const getActiveProject = useProjectStore((state) => state.getActiveProject);
|
||||
|
||||
const checkCompatibility = useCallback(async () => {
|
||||
const project = getActiveProject();
|
||||
if (!project) {
|
||||
setWarning(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsChecking(true);
|
||||
|
||||
try {
|
||||
// Get methodology config for the project
|
||||
const configResult = await window.electronAPI.getMethodologyConfig(project.path);
|
||||
|
||||
if (!configResult.success || !configResult.data) {
|
||||
// No methodology config - assume native, which is always compatible
|
||||
setWarning(null);
|
||||
setIsChecking(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const config: ProjectMethodologyConfig = configResult.data;
|
||||
|
||||
// Native methodology is always compatible
|
||||
if (isNativeMethodology(config.name)) {
|
||||
setWarning(null);
|
||||
setIsChecking(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if already dismissed for this project+methodology combo in this session
|
||||
const dismissKey = `${project.id}:${config.name}:${config.version}`;
|
||||
if (dismissedForSession.has(dismissKey)) {
|
||||
setWarning(null);
|
||||
setIsChecking(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check version compatibility
|
||||
const compatResult = await window.electronAPI.checkMethodologyCompatibility(
|
||||
config.name,
|
||||
config.version
|
||||
);
|
||||
|
||||
if (!compatResult.success || !compatResult.data) {
|
||||
// Error checking compatibility - don't show warning
|
||||
setWarning(null);
|
||||
setIsChecking(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const compatibility: MethodologyCompatibilityResult = compatResult.data;
|
||||
|
||||
if (!compatibility.compatible && compatibility.warning) {
|
||||
setWarning({
|
||||
showWarning: true,
|
||||
methodologyName: config.name,
|
||||
installedVersion: config.version,
|
||||
warningMessage: compatibility.warning,
|
||||
minVersion: compatibility.minVersion,
|
||||
maxVersion: compatibility.maxVersion,
|
||||
});
|
||||
} else {
|
||||
setWarning(null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking methodology compatibility:', error);
|
||||
setWarning(null);
|
||||
} finally {
|
||||
setIsChecking(false);
|
||||
}
|
||||
}, [getActiveProject, dismissedForSession]);
|
||||
|
||||
// Check compatibility when active project changes
|
||||
useEffect(() => {
|
||||
checkCompatibility();
|
||||
}, [activeProjectId, checkCompatibility]);
|
||||
|
||||
const dismissWarning = useCallback(() => {
|
||||
const project = getActiveProject();
|
||||
if (project && warning) {
|
||||
// Remember dismissal for this session
|
||||
const dismissKey = `${project.id}:${warning.methodologyName}:${warning.installedVersion}`;
|
||||
setDismissedForSession((prev) => new Set(prev).add(dismissKey));
|
||||
}
|
||||
setWarning(null);
|
||||
}, [getActiveProject, warning]);
|
||||
|
||||
const continueAnyway = useCallback(() => {
|
||||
dismissWarning();
|
||||
}, [dismissWarning]);
|
||||
|
||||
const switchToNative = useCallback(async () => {
|
||||
const project = getActiveProject();
|
||||
if (!project) return;
|
||||
|
||||
try {
|
||||
// Save native methodology config
|
||||
await window.electronAPI.saveMethodologyConfig(project.path, {
|
||||
name: 'native',
|
||||
version: '1.0.0',
|
||||
source: 'native',
|
||||
installedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Update project settings
|
||||
await window.electronAPI.updateProjectSettings(project.id, {
|
||||
methodology: 'native',
|
||||
});
|
||||
|
||||
// Clear warning
|
||||
setWarning(null);
|
||||
} catch (error) {
|
||||
console.error('Error switching to native methodology:', error);
|
||||
}
|
||||
}, [getActiveProject]);
|
||||
|
||||
return {
|
||||
warning,
|
||||
isChecking,
|
||||
dismissWarning,
|
||||
continueAnyway,
|
||||
switchToNative,
|
||||
recheckCompatibility: checkCompatibility,
|
||||
};
|
||||
}
|
||||
@@ -350,7 +350,50 @@ const browserMockAPI: ElectronAPI = {
|
||||
openLogsFolder: async () => ({ success: false, error: 'Not available in browser mode' }),
|
||||
copyDebugInfo: async () => ({ success: false, error: 'Not available in browser mode' }),
|
||||
getRecentErrors: async () => [],
|
||||
listLogFiles: async () => []
|
||||
listLogFiles: async () => [],
|
||||
|
||||
// Methodology Plugin Operations
|
||||
checkMethodologyInstalled: async (_projectPath: string, name: string) => ({
|
||||
success: true,
|
||||
data: {
|
||||
success: name === 'native',
|
||||
version: name === 'native' ? '1.0.0' : undefined,
|
||||
error: name === 'native' ? undefined : 'Not installed (browser mock)'
|
||||
}
|
||||
}),
|
||||
installMethodology: async (_projectPath: string, _name: string, _version?: string) => ({
|
||||
success: false,
|
||||
data: {
|
||||
success: false,
|
||||
error: 'Installation not available in browser mode'
|
||||
}
|
||||
}),
|
||||
getMethodologyConfig: async (_projectPath: string) => ({
|
||||
success: true,
|
||||
data: {
|
||||
name: 'native',
|
||||
version: '1.0.0',
|
||||
source: 'native' as const,
|
||||
installedAt: new Date().toISOString()
|
||||
}
|
||||
}),
|
||||
saveMethodologyConfig: async (_projectPath: string, _config: any) => ({
|
||||
success: true,
|
||||
data: undefined
|
||||
}),
|
||||
listAvailableMethodologies: async () => ({
|
||||
success: true,
|
||||
data: [
|
||||
{ name: 'native', type: 'native', verification: 'native', minVersion: '1.0.0' },
|
||||
{ name: 'bmad', type: 'npm', verification: 'verified', packageName: 'bmad-method', minVersion: '1.0.0' }
|
||||
]
|
||||
}),
|
||||
checkMethodologyCompatibility: async (_name: string, _version: string) => ({
|
||||
success: true,
|
||||
data: {
|
||||
compatible: true
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -183,5 +183,10 @@ export const infrastructureMock = {
|
||||
openTerminal: async (dirPath: string) => {
|
||||
console.warn('[Browser Mock] openTerminal:', dirPath);
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
openTerminalWithCommand: async (command: string, cwd?: string) => {
|
||||
console.warn('[Browser Mock] openTerminalWithCommand:', command, 'cwd:', cwd);
|
||||
return { success: true };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -35,3 +35,6 @@ export * from './api-profiles';
|
||||
|
||||
// Configuration and paths
|
||||
export * from './config';
|
||||
|
||||
// Methodology plugin sources
|
||||
export * from './methodology-sources';
|
||||
|
||||
@@ -152,6 +152,7 @@ export const IPC_CHANNELS = {
|
||||
// Shell operations
|
||||
SHELL_OPEN_EXTERNAL: 'shell:openExternal',
|
||||
SHELL_OPEN_TERMINAL: 'shell:openTerminal',
|
||||
SHELL_OPEN_TERMINAL_WITH_COMMAND: 'shell:openTerminalWithCommand',
|
||||
|
||||
// Roadmap operations
|
||||
ROADMAP_GET: 'roadmap:get',
|
||||
@@ -538,5 +539,13 @@ export const IPC_CHANNELS = {
|
||||
// Sentry error reporting
|
||||
SENTRY_STATE_CHANGED: 'sentry:state-changed', // Notify main process when setting changes
|
||||
GET_SENTRY_DSN: 'sentry:get-dsn', // Get DSN from main process (env var)
|
||||
GET_SENTRY_CONFIG: 'sentry:get-config' // Get full Sentry config (DSN + sample rates)
|
||||
GET_SENTRY_CONFIG: 'sentry:get-config', // Get full Sentry config (DSN + sample rates)
|
||||
|
||||
// Methodology plugin operations
|
||||
METHODOLOGY_CHECK_INSTALLED: 'methodology:checkInstalled', // Check if methodology is installed in project
|
||||
METHODOLOGY_INSTALL: 'methodology:install', // Install methodology
|
||||
METHODOLOGY_GET_CONFIG: 'methodology:getConfig', // Get project's methodology config
|
||||
METHODOLOGY_SAVE_CONFIG: 'methodology:saveConfig', // Save project's methodology config
|
||||
METHODOLOGY_LIST_AVAILABLE: 'methodology:listAvailable', // List available methodologies with their sources
|
||||
METHODOLOGY_CHECK_COMPATIBILITY: 'methodology:checkCompatibility' // Check version compatibility
|
||||
} as const;
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Methodology plugin source definitions for multiple distribution types.
|
||||
*
|
||||
* This mirrors the backend sources.py configuration, enabling consistent
|
||||
* handling of methodology sources across frontend and backend.
|
||||
*
|
||||
* Plugin Classification:
|
||||
* - Native: The built-in AutoClaude methodology (not a plugin, always available)
|
||||
* - Verified: Plugins maintained and tested by Auto Claude team
|
||||
* - Community: Third-party/user-created plugins without verification guarantee
|
||||
*/
|
||||
|
||||
/**
|
||||
* Distribution source types for methodology plugins.
|
||||
*/
|
||||
export type SourceType = 'native' | 'npm' | 'github' | 'local';
|
||||
|
||||
/**
|
||||
* Verification status for methodology plugins.
|
||||
*/
|
||||
export type VerificationStatus = 'native' | 'verified' | 'community';
|
||||
|
||||
/**
|
||||
* Configuration for a methodology's distribution source.
|
||||
*/
|
||||
export interface MethodologySource {
|
||||
/** Distribution source type */
|
||||
type: SourceType;
|
||||
/** Verification status */
|
||||
verification: VerificationStatus;
|
||||
/** Package identifier (NPM: package name, GitHub: owner/repo) */
|
||||
packageName?: string;
|
||||
/** Override default install command */
|
||||
installCommand?: string;
|
||||
/** Command to check installed version */
|
||||
versionCommand?: string;
|
||||
/** Minimum supported version for verified plugins */
|
||||
minVersion: string;
|
||||
/** Maximum supported version (exclusive) */
|
||||
maxVersion?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registry of known methodologies and their sources.
|
||||
* Verified plugins are maintained by Auto Claude and tested for compatibility.
|
||||
*/
|
||||
export const METHODOLOGY_SOURCES: Record<string, MethodologySource> = {
|
||||
native: {
|
||||
type: 'native',
|
||||
verification: 'native',
|
||||
minVersion: '1.0.0',
|
||||
},
|
||||
bmad: {
|
||||
type: 'npm',
|
||||
verification: 'verified',
|
||||
packageName: 'bmad-method',
|
||||
installCommand: 'npx bmad-method@alpha install',
|
||||
versionCommand: 'npx bmad-method@alpha --version',
|
||||
minVersion: '1.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the source configuration for a methodology.
|
||||
*/
|
||||
export function getMethodologySource(name: string): MethodologySource | undefined {
|
||||
return METHODOLOGY_SOURCES[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a methodology is verified (maintained by Auto Claude team).
|
||||
*/
|
||||
export function isVerifiedMethodology(name: string): boolean {
|
||||
const source = getMethodologySource(name);
|
||||
if (!source) return false;
|
||||
return source.verification === 'native' || source.verification === 'verified';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a methodology is the native (built-in) methodology.
|
||||
*/
|
||||
export function isNativeMethodology(name: string): boolean {
|
||||
const source = getMethodologySource(name);
|
||||
if (!source) return false;
|
||||
return source.verification === 'native';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the install command for a methodology.
|
||||
*/
|
||||
export function getInstallCommand(name: string): string | undefined {
|
||||
const source = getMethodologySource(name);
|
||||
if (!source || source.type === 'native') return undefined;
|
||||
return source.installCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all registered methodology names.
|
||||
*/
|
||||
export function listAvailableMethodologyNames(): string[] {
|
||||
return Object.keys(METHODOLOGY_SOURCES);
|
||||
}
|
||||
|
||||
/**
|
||||
* User-friendly display names for verification statuses.
|
||||
*/
|
||||
export const VERIFICATION_DISPLAY_NAMES: Record<VerificationStatus, string> = {
|
||||
native: 'Built-in',
|
||||
verified: 'Verified',
|
||||
community: 'Community',
|
||||
};
|
||||
|
||||
/**
|
||||
* User-friendly display names for source types.
|
||||
*/
|
||||
export const SOURCE_TYPE_DISPLAY_NAMES: Record<SourceType, string> = {
|
||||
native: 'Built-in',
|
||||
npm: 'NPM Package',
|
||||
github: 'GitHub',
|
||||
local: 'Local',
|
||||
};
|
||||
@@ -325,6 +325,13 @@
|
||||
"badge": {
|
||||
"native": "Native",
|
||||
"bmad": "BMAD"
|
||||
},
|
||||
"versionWarning": {
|
||||
"title": "Methodology Version Warning",
|
||||
"description": "The installed {{methodology}} version ({{version}}) may not be fully compatible with Auto Claude. Recommended: {{minVersion}}+",
|
||||
"riskNotice": "This could cause unexpected behavior during task execution. You can continue anyway or switch to the native methodology.",
|
||||
"continueAnyway": "Continue Anyway",
|
||||
"useNative": "Use Native"
|
||||
}
|
||||
},
|
||||
"bulkPR": {
|
||||
|
||||
@@ -325,6 +325,13 @@
|
||||
"badge": {
|
||||
"native": "Native",
|
||||
"bmad": "BMAD"
|
||||
},
|
||||
"versionWarning": {
|
||||
"title": "Avertissement de version de méthodologie",
|
||||
"description": "La version installée de {{methodology}} ({{version}}) peut ne pas être entièrement compatible avec Auto Claude. Recommandé : {{minVersion}}+",
|
||||
"riskNotice": "Cela pourrait causer un comportement inattendu lors de l'exécution des tâches. Vous pouvez continuer quand même ou passer à la méthodologie native.",
|
||||
"continueAnyway": "Continuer quand même",
|
||||
"useNative": "Utiliser Native"
|
||||
}
|
||||
},
|
||||
"bulkPR": {
|
||||
|
||||
@@ -25,6 +25,11 @@ import type {
|
||||
McpHealthCheckResult,
|
||||
McpTestConnectionResult
|
||||
} from './project';
|
||||
import type {
|
||||
ProjectMethodologyConfig,
|
||||
MethodologyInstallResult,
|
||||
MethodologyCompatibilityResult
|
||||
} from './methodology';
|
||||
import type {
|
||||
Task,
|
||||
TaskStatus,
|
||||
@@ -626,6 +631,8 @@ export interface ElectronAPI {
|
||||
// Shell operations
|
||||
openExternal: (url: string) => Promise<void>;
|
||||
openTerminal: (dirPath: string) => Promise<IPCResult<void>>;
|
||||
/** Open user's preferred terminal with a command. Optionally cd to a directory first. */
|
||||
openTerminalWithCommand: (command: string, cwd?: string) => Promise<IPCResult<void>>;
|
||||
|
||||
// Auto Claude source environment operations
|
||||
getSourceEnv: () => Promise<IPCResult<SourceEnvConfig>>;
|
||||
@@ -814,6 +821,21 @@ export interface ElectronAPI {
|
||||
// MCP Server health check operations
|
||||
checkMcpHealth: (server: CustomMcpServer) => Promise<IPCResult<McpHealthCheckResult>>;
|
||||
testMcpConnection: (server: CustomMcpServer) => Promise<IPCResult<McpTestConnectionResult>>;
|
||||
|
||||
// Methodology Plugin operations
|
||||
checkMethodologyInstalled: (projectPath: string, name: string) => Promise<IPCResult<MethodologyInstallResult>>;
|
||||
installMethodology: (projectPath: string, name: string, version?: string) => Promise<IPCResult<MethodologyInstallResult>>;
|
||||
getMethodologyConfig: (projectPath: string) => Promise<IPCResult<ProjectMethodologyConfig | null>>;
|
||||
saveMethodologyConfig: (projectPath: string, config: ProjectMethodologyConfig) => Promise<IPCResult<void>>;
|
||||
listAvailableMethodologies: () => Promise<IPCResult<Array<{
|
||||
name: string;
|
||||
type: string;
|
||||
verification: string;
|
||||
packageName?: string;
|
||||
minVersion: string;
|
||||
maxVersion?: string;
|
||||
}>>>;
|
||||
checkMethodologyCompatibility: (name: string, version: string) => Promise<IPCResult<MethodologyCompatibilityResult>>;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* Methodology plugin types for task execution
|
||||
*/
|
||||
|
||||
import type { SourceType, VerificationStatus } from '../constants/methodology-sources';
|
||||
|
||||
/**
|
||||
* Information about an available methodology plugin
|
||||
*/
|
||||
@@ -23,3 +25,62 @@ export interface MethodologyInfo {
|
||||
/** Install path of the methodology */
|
||||
install_path?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Project-level methodology configuration.
|
||||
* Stored in .auto-claude/methodology.json
|
||||
*/
|
||||
export interface ProjectMethodologyConfig {
|
||||
/** Name of the methodology (e.g., 'native', 'bmad') */
|
||||
name: string;
|
||||
/** Installed version of the methodology */
|
||||
version: string;
|
||||
/** How the methodology was installed */
|
||||
source: SourceType;
|
||||
/** ISO timestamp when the methodology was installed/configured */
|
||||
installedAt?: string;
|
||||
/** Package identifier for npm/github sources */
|
||||
packageName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of a methodology installation operation
|
||||
*/
|
||||
export interface MethodologyInstallResult {
|
||||
/** Whether the installation was successful */
|
||||
success: boolean;
|
||||
/** Installed version (on success) */
|
||||
version?: string;
|
||||
/** Error message (on failure) */
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of a methodology version compatibility check
|
||||
*/
|
||||
export interface MethodologyCompatibilityResult {
|
||||
/** Whether the version is compatible */
|
||||
compatible: boolean;
|
||||
/** Warning message if not fully compatible */
|
||||
warning?: string;
|
||||
/** Installed version being checked */
|
||||
installedVersion?: string;
|
||||
/** Minimum required version */
|
||||
minVersion?: string;
|
||||
/** Maximum supported version */
|
||||
maxVersion?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended methodology info with installation status
|
||||
*/
|
||||
export interface MethodologyWithStatus extends MethodologyInfo {
|
||||
/** Whether this methodology is installed in the current project */
|
||||
isInstalled: boolean;
|
||||
/** Source type (native, npm, github, local) */
|
||||
sourceType: SourceType;
|
||||
/** Verification status (native, verified, community) */
|
||||
verification: VerificationStatus;
|
||||
/** Compatibility status if version checked */
|
||||
compatibility?: MethodologyCompatibilityResult;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ export interface ProjectSettings {
|
||||
mainBranch?: string;
|
||||
/** Include CLAUDE.md instructions in agent system prompt (default: true) */
|
||||
useClaudeMd?: boolean;
|
||||
/** Default methodology name for this project (default: 'native') */
|
||||
methodology?: string;
|
||||
}
|
||||
|
||||
export interface NotificationSettings {
|
||||
|
||||
Reference in New Issue
Block a user