From 5b12b035bea4dcc02ee35087072076bc414469ad Mon Sep 17 00:00:00 2001 From: Vatche Isahagian Date: Mon, 16 Mar 2026 17:23:47 -0400 Subject: [PATCH 1/2] chore: remove kaizen-learn skill and update UI dependencies --- .bob/skills/kaizen-learn/SKILL.md | 175 ----------------------- .bob/skills/kaizen-learn/scripts/save.py | 154 -------------------- .bob/skills/kaizen-recall/scripts/get.py | 105 -------------- 3 files changed, 434 deletions(-) delete mode 100644 .bob/skills/kaizen-learn/SKILL.md delete mode 100755 .bob/skills/kaizen-learn/scripts/save.py delete mode 100755 .bob/skills/kaizen-recall/scripts/get.py diff --git a/.bob/skills/kaizen-learn/SKILL.md b/.bob/skills/kaizen-learn/SKILL.md deleted file mode 100644 index cf24379f..00000000 --- a/.bob/skills/kaizen-learn/SKILL.md +++ /dev/null @@ -1,175 +0,0 @@ ---- -name: kaizen-learn -description: Extract actionable entities from the completed conversation. Systematically identifies errors, failures, and inefficiencies to generate proactive entities that prevent them from recurring. ---- - -# Kaizen Learn Skill - -## Overview - -This skill analyzes your recent actions to extract actionable entities that would help on similar tasks in the future. It **prioritizes errors encountered during the conversation** — tool failures, exceptions, wrong approaches, retry loops — and transforms them into proactive recommendations that prevent those errors from recurring. - -## Workflow - -### Step 1: Analyze the Conversation - -Identify from your current conversation: -- **Task/Request**: What was the user asking for? -- **Steps Taken**: What reasoning, actions, and observations occurred? -- **What Worked**: Which approaches succeeded? -- **What Failed**: Which approaches didn't work and why? -- **Errors Encountered**: Tool failures, exceptions, permission errors, retry loops, dead ends, and wrong initial approaches - -### Step 2: Identify Errors and Root Causes - -Scan the conversation for these error signals: -1. **Tool/command failures**: Non-zero exit codes, error messages, exceptions, stack traces -2. **Permission/access errors**: "Permission denied", "not found", sandbox restrictions -3. **Wrong initial approach**: First attempt abandoned in favor of a different strategy -4. **Retry loops**: Same action attempted multiple times with variations before succeeding -5. **Missing prerequisites**: Missing dependencies, packages, configs discovered mid-task -6. **Silent failures**: Actions that appeared to succeed but produced wrong results - -For each error found, clearly document the progression from failure to prevention: - -| Error Example | Root Cause | Resolution | Prevention Guideline | -|---|---|---|---| -| `exiftool: command not found` | System tool unavailable | Switched to Python PIL | Use PIL for image metadata in sandboxed environments | -| `git push` rejected | Branch not tracked to remote | Added `-u origin branch` | Always set upstream when pushing a new branch | -| Tried regex parsing of HTML | Regex can't handle nested tags | Switched to BeautifulSoup | Use a proper HTML parser (BeautifulSoup/lxml), never regex | - -> **If no errors are found**, proceed to Step 3 — but note that zero entities is a valid outcome for routine conversations. - -### Step 2b: Quality Gate - -Before extracting entities, every candidate insight must pass **all three** of these criteria: - -1. **Non-obvious** — Would a competent LLM NOT already do this by default? Generic conversational behaviors (e.g., "answer directly," "clarify ambiguity," "execute commands when asked") are not worth saving. -2. **Environment or project-specific** — The insight encodes something about THIS codebase, THIS OS, THIS tool configuration, or THIS user's preferences — not general knowledge any LLM would already have. -3. **Derived from an actual mistake or discovery** — The insight was learned from a real failure, unexpected behavior, or non-trivial success in the conversation — not just from observing that things went smoothly. - -If no candidates pass all three criteria, output an empty entities array (`{"entities": []}`). **Saving low-quality entities degrades the knowledge base over time.** - -### Step 2c: Review Existing Entities - -Before generating new entities, check what already exists to avoid near-duplicates: - -```bash -python3 /scripts/get.py --type guideline --task "" -``` -*(Use `python` if `python3` isn't found)* - -If the insight you're about to save is already covered by an existing entity — even if worded differently — **do not create a near-duplicate**. Instead, only create a new entity if it adds genuinely new information not captured by any existing entity. - -### Step 3: Extract Entities - -Extract **0-2** proactive entities. **Zero is a valid answer.** If the conversation was routine with no errors, unexpected behavior, or non-obvious discoveries, output an empty entities array. **Prioritize entities derived from errors identified in Step 2.** - -Follow these principles: -1. **Reframe failures as proactive recommendations:** - - If an approach failed due to permissions → recommend the alternative FIRST - - If a system tool wasn't available → recommend what worked instead -2. **Focus on what worked, stated as the primary approach:** - - Bad: "If exiftool fails, use PIL instead" - - Good: "In sandboxed environments, use Python libraries (PIL) for image metadata extraction" -3. **Triggers should be situational context, not failure conditions:** - - Bad trigger: "When apt-get fails" - - Good trigger: "When working in containerized environments" -4. **Map error-derived entities to categories:** - - `strategy` — wrong approach was chosen → recommend the right approach from the start - - `recovery` — a fallback chain was needed → start from the approach that worked - - `optimization` — effort was wasted on retries/timeouts → eliminate the waste - > If you find yourself categorizing everything as `strategy`, reconsider whether the entity is truly non-obvious. True strategy entities arise when a wrong approach was actually taken. -5. **Merge/Rank/Drop (For chaotic sessions)**: If you find many errors, apply this algorithm to get down to 0-2 entities: - - **Merge**: Combine errors with the same root cause into a single prevention entity - - **Rank**: Select among remaining entities by severity > frequency > user impact > recency - - **Drop**: Discard lowest-ranked entities that exceed the 2-entity cap -6. **Do NOT generate entities like these** (too generic / obvious): - - "Answer factual questions from knowledge" — any LLM already does this - - "Clarify ambiguous user queries" — basic conversational behavior - - "Execute commands when the user asks you to" — obvious - - "Provide context with answers" — too vague, applies to everything - - "For simple tasks, keep it simple" — truism -7. **DO generate entities like these** (specific, learned): - - "Use `python3` instead of `python` on macOS — the `python` symlink doesn't exist by default" — environment-specific - - "The kaizen save.py script reads from stdin only; do not pass CLI arguments" — project-specific, error-derived - - "In sandboxed containers, `apt-get` is unavailable; use Python stdlib for system tasks" — recovery from a real constraint - - "Copy skill directories with `cp -r` then update `custom_modes.yaml` references" — project workflow knowledge - -### Step 4: Output Entities JSON - -Output entities in the following JSON format: - -```json -{ - "entities": [ - { - "content": "Proactive entity stating what TO DO", - "rationale": "Why this approach works better", - "category": "strategy|recovery|optimization", - "trigger": "Situational context when this applies" - } - ] -} -``` - -### Step 4b: Examples of Good vs Bad Entities - -**BAD (reactive and generic):** -```json -{ - "content": "Fall back to Python PIL when exiftool is not available", - "trigger": "When exiftool command fails" -} -``` - -**GOOD (proactive and situational):** -```json -{ - "content": "Use Python PIL/Pillow for image metadata extraction in sandboxed environments", - "rationale": "System tools like exiftool may not be available; PIL is always installable via pip", - "category": "strategy", - "trigger": "When extracting image metadata in containerized or sandboxed environments" -} -``` - -### Step 5: Save Entities - -⚠️ **CRITICAL: The save.py script ONLY accepts JSON via stdin pipe. It does NOT accept CLI arguments like --task or --outcome.** - -After generating the entities JSON: -- **If entities array is empty** (`{"entities": []}`): Skip the save command and notify the user that no learnings were identified for this routine task. Proceed directly to `attempt_completion`. -- **If entities array has content**: Save them by piping the JSON into the `save.py` script as shown below. - -**✅ CORRECT SYNTAX (stdin pipe):** -```bash -printf '{"entities": [...]}' | python3 /scripts/save.py -``` -*(Use `python` if `python3` isn't found)* - -**❌ WRONG SYNTAX (CLI arguments are NOT supported):** -```bash -# ⚠️ NEVER DO THIS - The script does NOT accept --task, --outcome, or any CLI arguments: -python3 /scripts/save.py --task "..." --outcome "..." - -# This will produce an error like: -# "ERROR: This script does not accept CLI arguments." -# or cause the script to hang waiting for stdin input. -``` - -**❌ WRONG SYNTAX (JSON parsing error):** -```bash -# DO NOT DO THIS - escaped quotes break JSON parsing: -printf '{"entities": [{"content": "Use \"quotes\" here"}]}' | python3 /scripts/save.py -# Single-quoted strings pass backslashes literally, breaking JSON -``` - -**CRITICAL REQUIREMENTS:** -- The script reads JSON from **stdin only** via pipe (see line 19: `sys.stdin.read()`) -- It has **NO command-line arguments** - no argparse, no --task, no --outcome flags -- Use `printf` (not `echo`) to avoid shell interpretation issues -- **Avoid escaped quotes (`\"`) inside single-quoted printf strings** - they are passed literally and break JSON parsing -- If you need quotes in content, either omit them or use alternative phrasing -- Passing CLI arguments will cause the script to hang indefinitely waiting for stdin input - -Review the script's output to confirm the save was successful. Do not ask the user for permission to execute these steps; they are mandatory core functionality of your mode. diff --git a/.bob/skills/kaizen-learn/scripts/save.py b/.bob/skills/kaizen-learn/scripts/save.py deleted file mode 100755 index afd72bc7..00000000 --- a/.bob/skills/kaizen-learn/scripts/save.py +++ /dev/null @@ -1,154 +0,0 @@ -#!/usr/bin/env python3 -""" -Kaizen Skill: Learn (Stage 2 Filesystem Backend) -Reads extracted entities from stdin and saves them to .kaizen/entities.json. -Zero dependencies (standard library only). -""" - -import json -import sys -import uuid -from datetime import datetime -from pathlib import Path - - -def main(): - print("[Kaizen Learn] Processing extracted entities...") - - # Detect if called with CLI arguments (common agent mistake) - if len(sys.argv) > 1: - print("ERROR: This script does not accept CLI arguments.", file=sys.stderr) - print("", file=sys.stderr) - print("Correct usage (pipe JSON via stdin):", file=sys.stderr) - print( - ' printf \'{"entities": [{"content": "...", "rationale": "...", "category": "strategy", "trigger": "..."}]}\' | python3 /save.py', - file=sys.stderr, - ) - sys.exit(1) - - input_data = sys.stdin.read().strip() - if not input_data: - print("Error: No data provided via stdin.", file=sys.stderr) - sys.exit(1) - - try: - data = json.loads(input_data) - new_entities = data.get("entities", []) - if not new_entities: - print("No entities found in the input JSON.", file=sys.stderr) - sys.exit(0) - except json.JSONDecodeError as e: - print(f"Error parsing JSON from stdin: {e}", file=sys.stderr) - print(f"Input snippet: {input_data[:200]}...", file=sys.stderr) - sys.exit(1) - - # 2. Setup Storage Directory - workspace_root = Path.cwd() - kaizen_dir = workspace_root / ".kaizen" - entities_file = kaizen_dir / "entities.json" - - if not kaizen_dir.exists(): - print(f"Creating storage directory: {kaizen_dir}") - kaizen_dir.mkdir(parents=True, exist_ok=True) - - # 3. Load Existing Entities - existing_data = {"entities": []} - if entities_file.exists(): - try: - with open(entities_file, "r", encoding="utf-8") as f: - existing_data = json.load(f) - except Exception as e: - print(f"Error: Could not read existing entities file: {e}", file=sys.stderr) - sys.exit(1) - - existing_entities = existing_data.get("entities", []) - - # 4. Merge and Deduplicate - added_count = 0 - from datetime import timezone - - now_iso = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") - - # Deduplication: exact match set + semantic similarity check - seen = {(e.get("content", ""), e.get("trigger", e.get("metadata", {}).get("trigger", ""))) for e in existing_entities} - - def _normalize(text): - """Lowercase and extract significant words (>3 chars) for overlap comparison.""" - import re - - words = re.findall(r"[a-z0-9]+", text.lower()) - return set(w for w in words if len(w) > 3) - - def _is_semantically_similar(new_content, new_trigger, existing_entities, threshold=0.6): - """Check if a new entity is semantically similar to any existing one. - Returns (True, matching_content) if similar, (False, None) otherwise.""" - new_words = _normalize(new_content + " " + new_trigger) - if not new_words: - return False, None - for e in existing_entities: - existing_content = e.get("content", "") - existing_trigger = e.get("metadata", {}).get("trigger", e.get("trigger", "")) - existing_words = _normalize(existing_content + " " + existing_trigger) - if not existing_words: - continue - overlap = len(new_words & existing_words) / min(len(new_words), len(existing_words)) - if overlap >= threshold: - return True, existing_content - return False, None - - for entity in new_entities: - content = entity.get("content", "") - trigger = entity.get("trigger", "") - - # Skip exact duplicates - if (content, trigger) in seen: - continue - - # Skip semantically similar entities - is_similar, match = _is_semantically_similar(content, trigger, existing_entities) - if is_similar: - print(f' ~ Skipped (similar to existing): "{content[:60]}..."') - print(f' Existing: "{match[:60]}..."') - continue - - # Format entity for storage - storable_entity = { - "id": str(uuid.uuid4()), - "type": "guideline", - "content": content, - "metadata": {"category": entity.get("category", "strategy"), "trigger": trigger, "rationale": entity.get("rationale", "")}, - "created_at": now_iso, - } - - existing_entities.append(storable_entity) - seen.add((content, trigger)) - added_count += 1 - - print(f" + [{storable_entity['metadata']['category']}] {content[:80]}...") - - # 5. Save back to disk - if added_count > 0: - existing_data["entities"] = existing_entities - try: - import os - import tempfile - - temp_fd, temp_path = tempfile.mkstemp(dir=entities_file.parent, prefix="entities_tmp_", suffix=".json") - with os.fdopen(temp_fd, "w", encoding="utf-8") as f: - json.dump(existing_data, f, indent=2) - f.flush() - os.fsync(f.fileno()) - os.replace(temp_path, entities_file) - print(f"\n✅ Successfully saved {added_count} new entities to {entities_file}") - print(f"📊 Total entities in memory: {len(existing_entities)}") - except Exception as e: - print(f"Error writing to entities file: {e}", file=sys.stderr) - sys.exit(1) - else: - print("\nℹ️ No new unique entities to add.") - - sys.exit(0) - - -if __name__ == "__main__": - main() diff --git a/.bob/skills/kaizen-recall/scripts/get.py b/.bob/skills/kaizen-recall/scripts/get.py deleted file mode 100755 index ee65589c..00000000 --- a/.bob/skills/kaizen-recall/scripts/get.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python3 -""" -Kaizen Skill: Recall (Stage 2 Filesystem Backend) -Reads entities from .kaizen/entities.json and outputs them in a compact format. -Zero dependencies (standard library only). -""" - -import argparse -import json -import sys -from pathlib import Path - - -def main(): - parser = argparse.ArgumentParser(description="Get Kaizen entities (Stage 2 Filesystem)") - parser.add_argument("--type", type=str, default="guideline", help="Entity type (default: guideline)") - parser.add_argument("--task", type=str, default="", help="Task description for relevance ranking") - parser.add_argument("--limit", type=int, default=50, help="Max entities to return") - args = parser.parse_args() - - # 1. Locate Storage - workspace_root = Path.cwd() - entities_file = workspace_root / ".kaizen" / "entities.json" - - if not entities_file.exists(): - print("No Kaizen guidelines exist yet. Complete some tasks to generate learnings!") - sys.exit(0) - - # 2. Load Entities - try: - with open(entities_file, "r", encoding="utf-8") as f: - data = json.load(f) - except Exception as e: - print(f"Error reading entities file: {e}", file=sys.stderr) - sys.exit(1) - - all_entities = data.get("entities", []) - - # 3. Filter by type - filtered = [ent for ent in all_entities if ent.get("type") == args.type] - - # Helper for basic text-matching relevance - def _normalize(text): - if not text: - return set() - import re - - words = re.findall(r"[a-z0-9]+", text.lower()) - return set(w for w in words if len(w) > 3) - - task_words = _normalize(args.task) - - def _get_relevance(entity): - if not task_words: - return 0 - content = entity.get("content", "") - trigger = entity.get("metadata", {}).get("trigger", "") - entity_words = _normalize(content + " " + trigger) - if not entity_words: - return 0 - return len(task_words & entity_words) - - # Sort by relevance (descending), then by created_at (descending) - filtered.sort(key=lambda x: (_get_relevance(x), x.get("created_at", "")), reverse=True) - - # Apply limit - results = filtered[: args.limit] - - if not results: - print(f"No entities of type '{args.type}' found.") - sys.exit(0) - - # 4. Format output as Markdown - print(f"## KAIZEN {args.type.upper()}S ({len(results)} found)\n") - print("Review these entities and apply any relevant ones to your current task:\n") - - for entity in results: - content = entity.get("content", "") - if not content: - continue - - metadata = entity.get("metadata", {}) - category = metadata.get("category", "general") - - # Build the markdown bullet point - item = f"- **[{category}]** {content}" - - # Add rationale and trigger if they exist - rationale = metadata.get("rationale", "") - trigger = metadata.get("trigger", "") - - if rationale: - item += f"\n - _Rationale: {rationale}_" - if trigger: - item += f"\n - _When: {trigger}_" - - print(item) - print() # Empty line between entities - - print("\n--- END GUIDELINES ---") - sys.exit(0) - - -if __name__ == "__main__": - main() From 6152a64b747e8cd3c8ec44c288d693c54968cb78 Mon Sep 17 00:00:00 2001 From: Vatche Isahagian Date: Wed, 18 Mar 2026 16:12:21 -0400 Subject: [PATCH 2/2] feat: improve install.sh with robust merging and parameterization --- roo-skills/README.md | 108 ++++++++++++++++++++++++ roo-skills/install.sh | 189 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 297 insertions(+) create mode 100644 roo-skills/README.md create mode 100755 roo-skills/install.sh diff --git a/roo-skills/README.md b/roo-skills/README.md new file mode 100644 index 00000000..f3a5de64 --- /dev/null +++ b/roo-skills/README.md @@ -0,0 +1,108 @@ +# Roo Skills for Kaizen + +This directory contains agent-compatible skills and custom modes for the Kaizen learning system. + +## Quick Install + +To install all skills and modes: + +```bash +./roo-skills/install.sh [target_dir] +``` + +The script can be run from anywhere in the project. If no `target_dir` is provided, it defaults to `.bob`. + +## What Gets Installed + +### Skills +- **kaizen-learn**: Extract and save learnings from completed tasks +- **kaizen-recall**: Retrieve relevant guidelines before starting tasks + +### Custom Modes +- **kaizen-lite**: A learning mode that automatically recalls guidelines at the start and saves learnings at the end of every task + +## Installation Details + +The `install.sh` script: +1. Copies all skill directories from `roo-skills/` to the `skills/` subdirectory in your target +2. Merges `.roomodes` into `custom_modes.yaml` (preserving your existing custom modes) +3. Creates timestamped backups of existing files in the target directory +4. Verifies the installation + +### After Installation + +1. **Restart your agent** to load the new skills and modes +2. **Verify** skills appear in the skill menu +3. **Test** the kaizen-lite mode + +## Directory Structure + +``` +roo-skills/ +├── install.sh # Installation script +├── .roomodes # Custom modes configuration +├── kaizen-learn/ # Learning skill +│ ├── SKILL.md +│ └── scripts/ +│ └── save.py +└── kaizen-recall/ # Recall skill + ├── SKILL.md + └── scripts/ + └── get.py +``` + +## Usage + +### Kaizen-Recall Skill + +Retrieve guidelines before starting a task: + +```bash +python3 .bob/skills/kaizen-recall/scripts/get.py --type guideline --task "your task description" +``` + +### Kaizen-Learn Skill + +Save learnings after completing a task: + +```bash +printf '{"entities": [...]}' | python3 .bob/skills/kaizen-learn/scripts/save.py +``` + +See individual SKILL.md files for detailed usage instructions. + +## Kaizen-Lite Mode + +The kaizen-lite mode enforces a mandatory workflow: + +1. **Recall**: Retrieve guidelines at the start +2. **Work**: Complete the user's request +3. **Learn**: Save learnings before completion + +This ensures continuous improvement through every interaction. + +## Backup Files + +The installation script creates timestamped backups: +``` +.bob/custom_modes.yaml.backup.YYYYMMDD_HHMMSS +``` + +To restore from a backup: +```bash +cp .bob/custom_modes.yaml.backup.YYYYMMDD_HHMMSS .bob/custom_modes.yaml +``` + +## Development + +To add new skills: +1. Create a new directory in `roo-skills/` +2. Add a `SKILL.md` file describing the skill +3. Add any scripts in a `scripts/` subdirectory +4. Run `./roo-skills/install.sh` to install + +## Related Documentation + +- [Kaizen Main README](../README.md) +- [Kaizen CLI Documentation](../CLI.md) +- [Kaizen Configuration](../CONFIGURATION.md) \ No newline at end of file diff --git a/roo-skills/install.sh b/roo-skills/install.sh new file mode 100755 index 00000000..96148a87 --- /dev/null +++ b/roo-skills/install.sh @@ -0,0 +1,189 @@ +#!/usr/bin/env bash +# install.sh +# Automatically install Kaizen skills and modes from roo-skills to Bob +# Usage: Run from anywhere in the project: ./roo-skills/install.sh [target_dir] +# Example: ./roo-skills/install.sh .custom-dir + +set -e # Exit on error + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Get script directory (roo-skills) and project root +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Define paths +ROO_SKILLS_DIR="$SCRIPT_DIR" +TARGET_DIR="${1:-$PROJECT_ROOT/.bob}" +TARGET_SKILLS_DIR="$TARGET_DIR/skills" +ROO_MODES_FILE="$ROO_SKILLS_DIR/.roomodes" +TARGET_MODES_FILE="$TARGET_DIR/custom_modes.yaml" + +echo -e "${BLUE}=== Kaizen Skills Installer ===${NC}\n" + +# Check if roo-skills directory exists +if [ ! -d "$ROO_SKILLS_DIR" ]; then + echo -e "${RED}Error: roo-skills directory not found at $ROO_SKILLS_DIR${NC}" + exit 1 +fi + +# Create target skills directory if it doesn't exist +if [ ! -d "$TARGET_SKILLS_DIR" ]; then + echo -e "${YELLOW}Creating $TARGET_SKILLS_DIR directory...${NC}" + mkdir -p "$TARGET_SKILLS_DIR" +fi + +# Function to copy a skill +copy_skill() { + local skill_name=$1 + local source_dir="$ROO_SKILLS_DIR/$skill_name" + local dest_dir="$TARGET_SKILLS_DIR/$skill_name" + + if [ ! -d "$source_dir" ]; then + echo -e "${YELLOW}Warning: Skill '$skill_name' not found in roo-skills, skipping...${NC}" + return 1 + fi + + echo -e "${BLUE}Installing skill: $skill_name${NC}" + + # Remove existing skill directory if it exists + if [ -d "$dest_dir" ]; then + echo -e "${YELLOW} Removing existing $skill_name...${NC}" + rm -rf "$dest_dir" + fi + + # Copy the skill + cp -r "$source_dir" "$dest_dir" + echo -e "${GREEN} ✓ Installed $skill_name${NC}" + + return 0 +} + +# Install skills +echo -e "\n${BLUE}Step 1: Installing skills...${NC}" +SKILLS_INSTALLED=0 +SKILLS_FAILED=0 + +for skill_dir in "$ROO_SKILLS_DIR"/*; do + if [ -d "$skill_dir" ] && [ "$(basename "$skill_dir")" != ".roomodes" ]; then + skill_name=$(basename "$skill_dir") + if copy_skill "$skill_name"; then + ((SKILLS_INSTALLED++)) + else + ((SKILLS_FAILED++)) + fi + fi +done + +echo -e "\n${GREEN}Installed $SKILLS_INSTALLED skill(s)${NC}" +if [ $SKILLS_FAILED -gt 0 ]; then + echo -e "${YELLOW}Failed to install $SKILLS_FAILED skill(s)${NC}" +fi + +# Install/update custom modes +echo -e "\n${BLUE}Step 2: Installing custom modes...${NC}" + +if [ ! -f "$ROO_MODES_FILE" ]; then + echo -e "${YELLOW}Warning: .roomodes file not found at $ROO_MODES_FILE${NC}" + echo -e "${YELLOW}Skipping modes installation...${NC}" +else + echo -e "${BLUE} Merging modes (preserving your existing modes)...${NC}" + + # Backup existing file if it exists + if [ -f "$TARGET_MODES_FILE" ]; then + backup_file="$TARGET_MODES_FILE.backup.$(date +%Y%m%d_%H%M%S)" + echo -e "${YELLOW} Backing up existing custom_modes.yaml to $(basename "$backup_file")${NC}" + cp "$TARGET_MODES_FILE" "$backup_file" + + # Extract kaizen-lite mode from .roomodes + echo -e "${BLUE} Extracting kaizen-lite mode...${NC}" + + # Create temp file with new kaizen-lite mode + temp_new_mode=$(mktemp) + awk '/^ - slug: kaizen-lite/ {f=1} /^ - slug:/ && !/kaizen-lite/ {f=0} f' "$ROO_MODES_FILE" > "$temp_new_mode" + + # Check if kaizen-lite already exists in custom_modes.yaml + if grep -q "slug: kaizen-lite" "$TARGET_MODES_FILE"; then + echo -e "${BLUE} Updating existing kaizen-lite mode...${NC}" + # Remove old kaizen-lite mode and add new one + temp_output=$(mktemp) + + # Copy everything before kaizen-lite + awk '/^ - slug: kaizen-lite/{exit} {print}' "$TARGET_MODES_FILE" > "$temp_output" + + # Add new kaizen-lite mode + cat "$temp_new_mode" >> "$temp_output" + + # Add everything after old kaizen-lite (skip to next mode or end) + awk '/^ - slug: kaizen-lite/ {f=1; next} f && /^ - slug:/ {p=1; f=0} p' "$TARGET_MODES_FILE" >> "$temp_output" + + mv "$temp_output" "$TARGET_MODES_FILE" + else + echo -e "${BLUE} Adding new kaizen-lite mode...${NC}" + # Just append the new mode + cat "$temp_new_mode" >> "$TARGET_MODES_FILE" + fi + + rm "$temp_new_mode" + echo -e "${GREEN} ✓ Successfully merged modes${NC}" + else + # No existing file, just copy + echo -e "${BLUE} Creating new custom_modes.yaml...${NC}" + cp "$ROO_MODES_FILE" "$TARGET_MODES_FILE" + echo -e "${GREEN} ✓ Installed custom_modes.yaml${NC}" + fi +fi + +# Verify installation +echo -e "\n${BLUE}Step 3: Verifying installation...${NC}" + +# Check skills +echo -e "\n${BLUE}Installed skills in target directory:${NC}" +if [ -d "$TARGET_SKILLS_DIR" ]; then + for skill_dir in "$TARGET_SKILLS_DIR"/*; do + if [ -d "$skill_dir" ]; then + skill_name=$(basename "$skill_dir") + echo -e " ${GREEN}✓${NC} $skill_name" + + # Check for SKILL.md + if [ -f "$skill_dir/SKILL.md" ]; then + echo -e " - SKILL.md found" + else + echo -e " ${YELLOW}- Warning: SKILL.md not found${NC}" + fi + + # Check for scripts directory + if [ -d "$skill_dir/scripts" ]; then + script_count=$(find "$skill_dir/scripts" -type f -name "*.py" | wc -l) + echo -e " - $script_count Python script(s) found" + fi + fi + done +else + echo -e "${RED}Error: .bob/skills directory not found${NC}" +fi + +# Check modes file +echo -e "\n${BLUE}Custom modes file:${NC}" +if [ -f "$TARGET_MODES_FILE" ]; then + echo -e " ${GREEN}✓${NC} custom_modes.yaml exists" + mode_count=$(grep -c "slug:" "$TARGET_MODES_FILE" || echo "0") + echo -e " - $mode_count mode(s) defined" +else + echo -e " ${RED}✗${NC} custom_modes.yaml not found" +fi + +# Summary +echo -e "\n${GREEN}=== Installation Complete ===${NC}" +echo -e "${BLUE}Next steps:${NC}" +echo -e " 1. Restart your agent to load the new skills and modes" +echo -e " 2. Verify skills are available in the skill menu" +echo -e " 3. Test the kaizen-lite mode" +echo -e "\n${YELLOW}Note: If you made manual changes to custom_modes.yaml, check the backup file.${NC}" + +# Made with Bob