From 5b12b035bea4dcc02ee35087072076bc414469ad Mon Sep 17 00:00:00 2001 From: Vatche Isahagian Date: Mon, 16 Mar 2026 17:23:47 -0400 Subject: [PATCH] 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()