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:
Test User
2026-01-19 13:26:48 +01:00
co-authored by Claude Opus 4.5
parent 0bd5236a04
commit c8eaeea6ea
26 changed files with 1612 additions and 31 deletions
+171
View File
@@ -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') {
+6 -1
View File
@@ -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;
}
+8 -3
View File
@@ -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)
});
+52 -1
View File
@@ -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,
};
}
+44 -1
View File
@@ -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';
+10 -1
View File
@@ -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": {
+22
View File
@@ -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 {