diff --git a/config.json b/config.json index 6232bd3..b9e8d81 100644 --- a/config.json +++ b/config.json @@ -169,6 +169,18 @@ } ] }, + "feature-dev": { + "enabled": false, + "description": "7-phase feature development workflow with codeagent orchestration", + "operations": [ + { + "type": "copy_dir", + "source": "skills/feature-dev", + "target": "skills/feature-dev", + "description": "Install feature-dev skill with hooks" + } + ] + }, "course": { "enabled": false, "description": "课程开发工作流,包含 dev、产品需求和测试用例技能", diff --git a/install.py b/install.py index 59364f2..291b148 100644 --- a/install.py +++ b/install.py @@ -121,8 +121,11 @@ def save_settings(ctx: Dict[str, Any], settings: Dict[str, Any]) -> None: _save_json(settings_path, settings) -def find_module_hooks(module_name: str, cfg: Dict[str, Any], ctx: Dict[str, Any]) -> Optional[Dict[str, Any]]: - """Find hooks.json for a module if it exists.""" +def find_module_hooks(module_name: str, cfg: Dict[str, Any], ctx: Dict[str, Any]) -> Optional[tuple]: + """Find hooks.json for a module if it exists. + + Returns tuple of (hooks_config, plugin_root_path) or None. + """ # Check for hooks in operations (copy_dir targets) for op in cfg.get("operations", []): if op.get("type") == "copy_dir": @@ -130,18 +133,19 @@ def find_module_hooks(module_name: str, cfg: Dict[str, Any], ctx: Dict[str, Any] hooks_file = target_dir / "hooks" / "hooks.json" if hooks_file.exists(): try: - return _load_json(hooks_file) + return (_load_json(hooks_file), str(target_dir)) except (ValueError, FileNotFoundError): pass # Also check source directory during install for op in cfg.get("operations", []): if op.get("type") == "copy_dir": + target_dir = ctx["install_dir"] / op["target"] source_dir = ctx["config_dir"] / op["source"] hooks_file = source_dir / "hooks" / "hooks.json" if hooks_file.exists(): try: - return _load_json(hooks_file) + return (_load_json(hooks_file), str(target_dir)) except (ValueError, FileNotFoundError): pass @@ -153,7 +157,18 @@ def _create_hook_marker(module_name: str) -> str: return f"__module:{module_name}__" -def merge_hooks_to_settings(module_name: str, hooks_config: Dict[str, Any], ctx: Dict[str, Any]) -> None: +def _replace_hook_variables(obj: Any, plugin_root: str) -> Any: + """Recursively replace ${CLAUDE_PLUGIN_ROOT} in hook config.""" + if isinstance(obj, str): + return obj.replace("${CLAUDE_PLUGIN_ROOT}", plugin_root) + elif isinstance(obj, dict): + return {k: _replace_hook_variables(v, plugin_root) for k, v in obj.items()} + elif isinstance(obj, list): + return [_replace_hook_variables(item, plugin_root) for item in obj] + return obj + + +def merge_hooks_to_settings(module_name: str, hooks_config: Dict[str, Any], ctx: Dict[str, Any], plugin_root: str = "") -> None: """Merge module hooks into settings.json.""" settings = load_settings(ctx) settings.setdefault("hooks", {}) @@ -161,6 +176,10 @@ def merge_hooks_to_settings(module_name: str, hooks_config: Dict[str, Any], ctx: module_hooks = hooks_config.get("hooks", {}) marker = _create_hook_marker(module_name) + # Replace ${CLAUDE_PLUGIN_ROOT} with actual path + if plugin_root: + module_hooks = _replace_hook_variables(module_hooks, plugin_root) + for hook_type, hook_entries in module_hooks.items(): settings["hooks"].setdefault(hook_type, []) @@ -707,10 +726,11 @@ def execute_module(name: str, cfg: Dict[str, Any], ctx: Dict[str, Any]) -> Dict[ raise # Handle hooks: find and merge module hooks into settings.json - hooks_config = find_module_hooks(name, cfg, ctx) - if hooks_config: + hooks_result = find_module_hooks(name, cfg, ctx) + if hooks_result: + hooks_config, plugin_root = hooks_result try: - merge_hooks_to_settings(name, hooks_config, ctx) + merge_hooks_to_settings(name, hooks_config, ctx, plugin_root) result["operations"].append({"type": "merge_hooks", "status": "success"}) result["has_hooks"] = True except Exception as exc: