Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "kaizen-marketplace",
"description": "Kaizen plugins for Claude Code - continuous improvement through auto-generated guidelines",
"owner": {
"name": "Vinod Muthusamy"
},
"plugins": [
{
"name": "kaizen",
"description": "Learn from conversations with auto-generated guidelines",
"version": "1.0.0",
"author": {
"name": "Vinod Muthusamy"
},
"source": "./plugins/kaizen",
"category": "productivity"
}
]
}
9 changes: 9 additions & 0 deletions plugins/kaizen/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "kaizen",
"version": "1.0.0",
"description": "Learn from conversations with auto-generated guidelines",
"author": {
"name": "Vinod Muthusamy"
},
"skills": "./skills/"
}
109 changes: 109 additions & 0 deletions plugins/kaizen/INSTALL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Installation Guide

## Prerequisites

- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed
- Python 3.8+ available in PATH

## Option 1: Load Local Plugin (Development)

Use `--plugin-dir` to load the plugin for a session:

```bash
# Load plugin when starting Claude Code (absolute path)
claude --plugin-dir /path/to/kaizen

# Or use a relative path from current directory
claude --plugin-dir ./kaizen
```

**Note:** The plugin is loaded **for that session only**.

### Loading Multiple Plugins

```bash
claude --plugin-dir ./plugin1 --plugin-dir ./plugin2
```

### Making It Permanent

Add an alias to your shell profile (`~/.bashrc` or `~/.zshrc`):

```bash
alias claude='claude --plugin-dir /path/to/kaizen'
```

Then reload your shell:

```bash
source ~/.zshrc # or source ~/.bashrc
```

## Option 2: Install from Marketplace (when published)

```bash
claude plugin install kaizen
```
Comment on lines +10 to +46

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find manifest files in plugins/kaizen directory
find plugins/kaizen -type f -name "manifest.*" -o -name "package.json" | head -20

Repository: AgentToolkit/kaizen

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Read the INSTALL.md file to see actual content
cat -n plugins/kaizen/INSTALL.md

Repository: AgentToolkit/kaizen

Length of output: 3304


🏁 Script executed:

#!/bin/bash
# Search for manifest files in the plugins/kaizen directory
find plugins/kaizen -type f \( -name "*.json" \) | grep -E "(manifest|package)" | head -20

Repository: AgentToolkit/kaizen

Length of output: 45


🏁 Script executed:

#!/bin/bash
# List all files in plugins/kaizen to understand structure
ls -la plugins/kaizen/

Repository: AgentToolkit/kaizen

Length of output: 524


🏁 Script executed:

#!/bin/bash
# Check the .claude-plugin directory
ls -la plugins/kaizen/.claude-plugin/

Repository: AgentToolkit/kaizen

Length of output: 231


🏁 Script executed:

#!/bin/bash
# Read manifest files in .claude-plugin
find plugins/kaizen/.claude-plugin -type f -exec cat {} \;

Repository: AgentToolkit/kaizen

Length of output: 257


Fix plugin name mismatch in install instructions.

The marketplace manifest registers this plugin as kaizen, but INSTALL.md uses guidelines and guidelines-plugin throughout. The marketplace install command on line 45 will fail, and users following the directory path examples will get incorrect commands.

Update all references to use kaizen:

🛠️ Proposed fix (align naming)
-claude --plugin-dir /path/to/guidelines-plugin
+claude --plugin-dir /path/to/kaizen

-claude --plugin-dir ./guidelines-plugin
+claude --plugin-dir ./kaizen

-alias claude='claude --plugin-dir /path/to/guidelines-plugin'
+alias claude='claude --plugin-dir /path/to/kaizen'

-claude plugin validate /path/to/guidelines-plugin
+claude plugin validate /path/to/kaizen

-claude --plugin-dir ./guidelines-plugin
+claude --plugin-dir ./kaizen

-run `claude plugin validate ./guidelines-plugin`
+run `claude plugin validate ./kaizen`

-check script permissions: `ls -la guidelines-plugin/scripts/`
+check script permissions: `ls -la kaizen/scripts/`

-claude plugin install guidelines
+claude plugin install kaizen
🤖 Prompt for AI Agents
In `@plugins/kaizen/INSTALL.md` around lines 10 - 46, Update INSTALL.md to use the
correct plugin name "kaizen" everywhere: replace instances of
"guidelines-plugin" and "guidelines" in the example commands and alias with
"kaizen" (e.g., change `claude --plugin-dir ./guidelines-plugin`, `alias
claude='claude --plugin-dir /path/to/guidelines-plugin'`, and `claude plugin
install guidelines` to use "kaizen"), ensuring the marketplace install command
`claude plugin install kaizen` and the plugin-dir examples reference the kaizen
plugin name consistently.


## Validate Plugin

Before using, validate the plugin manifest:

```bash
claude plugin validate /path/to/kaizen
# Should output: ✔ Validation passed
```

## Initialize Guidelines File (Optional)

```bash
mkdir -p .claude
echo '{"guidelines": []}' > .claude/guidelines.json
```

## Verification

After loading the plugin, verify it's working:

1. **Test hook execution:**
```bash
# Start Claude Code with the plugin
claude --plugin-dir ./kaizen

# Send any prompt, then check the debug log (in another terminal)
cat /tmp/guidelines-plugin.log
# Should show: "[retrieve] Script started" with timestamp
```

2. **Test guideline storage:**
```bash
# After ending a conversation where guidelines were generated
cat .claude/guidelines.json
# Should contain the extracted guidelines
```

3. **Test skills manually:**
```bash
# In a Claude Code session with the plugin loaded, invoke:
/guidelines:generator
/guidelines:retrieval
```

## Troubleshooting

### Plugin validation fails

Run `claude plugin validate ./kaizen` to see specific errors.

### Hooks not firing

1. Verify the plugin is loaded with `--plugin-dir`
2. Verify Python is in PATH: `which python3`
3. Check script permissions: `ls -la kaizen/scripts/`
4. Check debug log: `cat /tmp/guidelines-plugin.log`

### Guidelines not saving

1. Verify `.claude/` directory exists in your project
2. Check write permissions on the directory
3. Review `/tmp/guidelines-plugin.log` for error messages
99 changes: 99 additions & 0 deletions plugins/kaizen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Guidelines Plugin for Claude Code

A plugin that helps Claude Code learn from conversations by automatically extracting and applying guidelines.

## Features

- **Automatic Learning**: At the end of each conversation, guidelines are extracted and saved
- **Context-Aware Retrieval**: At the start of each prompt, relevant guidelines are injected
- **No Manual Configuration**: Hooks are automatically installed when the plugin is enabled

## Installation

```bash
# Load plugin for current session
claude --plugin-dir ./kaizen

# Or with absolute path
claude --plugin-dir /path/to/kaizen
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

See [INSTALL.md](INSTALL.md) for making it permanent, loading multiple plugins, and troubleshooting.

## How It Works

### Guideline Retrieval (UserPromptSubmit hook)

When you submit a prompt, the plugin:
1. Loads all stored guidelines from `.claude/guidelines.json`
2. Formats and injects them into the conversation context
3. Claude applies relevant guidelines to the current task

### Guideline Generation (Stop hook)

When a conversation ends, the plugin:
1. Analyzes the conversation trajectory
2. Extracts actionable guidelines from what worked/failed
3. Saves new guidelines to `.claude/guidelines.json`
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## Skills Included

### `/guidelines:generator`

Manually invoke to extract guidelines from the current conversation:
- Analyzes task, steps taken, successes and failures
- Generates proactive guidelines (what to do, not what to avoid)
- Outputs JSON for storage

### `/guidelines:retrieval`

Manually invoke to retrieve and display stored guidelines.

## Guidelines Storage

Guidelines are stored in `.claude/guidelines.json`:

```json
{
"guidelines": [
{
"content": "Use Python PIL/Pillow for image metadata extraction in sandboxed environments",
"rationale": "System tools like exiftool may not be available",
"category": "strategy",
"trigger": "When extracting image metadata in containerized environments"
}
]
}
```

## Environment Variables

- `GUIDELINES_FILE`: Override the default guidelines storage location
- `CLAUDE_PROJECT_ROOT`: Set by Claude Code, used to locate project-level guidelines

## Verification

After installation, run `claude plugin list` to confirm the plugin is enabled. See [INSTALL.md](INSTALL.md) for detailed verification steps.

## Plugin Structure

```text
kaizen/
├── .claude-plugin/
│ └── plugin.json # Plugin manifest
├── skills/
│ ├── guideline-generator/
│ │ └── SKILL.md
│ └── guideline-retrieval/
│ └── SKILL.md
├── hooks/
│ └── hooks.json # Auto-configured hooks
├── scripts/
│ ├── save_guidelines.py
│ └── retrieve_guidelines.py
└── README.md
```

## License

MIT
26 changes: 26 additions & 0 deletions plugins/kaizen/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"hooks": {
"UserPromptSubmit": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/scripts/retrieve_guidelines.py"
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/scripts/save_guidelines.py"
}
]
}
]
}
}
101 changes: 101 additions & 0 deletions plugins/kaizen/scripts/retrieve_guidelines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""Retrieve and output guidelines for Claude to filter."""

import json
import os
import sys
from pathlib import Path
import datetime

# Debug logging
LOG_FILE = "/tmp/guidelines-plugin.log"

def log(message):
"""Append a timestamped message to the log file."""
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(LOG_FILE, "a") as f:
f.write(f"[{timestamp}] [retrieve] {message}\n")
Comment on lines +10 to +17

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid fixed log file under /tmp (predictable path).

Use a safer, configurable location (project .claude or user state dir) and ensure the parent directory exists before writing.

🔒 Proposed fix (configurable, safer log path)
-# Debug logging
-LOG_FILE = "/tmp/guidelines-plugin.log"
+# Debug logging
+LOG_FILE = os.environ.get(
+    "GUIDELINES_LOG_FILE",
+    str(
+        (
+            Path(os.environ.get("CLAUDE_PROJECT_ROOT", ""))
+            if os.environ.get("CLAUDE_PROJECT_ROOT")
+            else Path.home() / ".local" / "state" / "kaizen"
+        )
+        / ".claude"
+        / "guidelines-plugin.log"
+    ),
+)

 def log(message):
     """Append a timestamped message to the log file."""
     timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
-    with open(LOG_FILE, "a") as f:
+    Path(LOG_FILE).parent.mkdir(parents=True, exist_ok=True)
+    with open(LOG_FILE, "a") as f:
         f.write(f"[{timestamp}] [retrieve] {message}\n")
🧰 Tools
🪛 Ruff (0.14.14)

11-11: Probable insecure usage of temporary file or directory: "/tmp/guidelines-plugin.log"

(S108)

🤖 Prompt for AI Agents
In `@plugins/kaizen/scripts/retrieve_guidelines.py` around lines 10 - 17, Replace
the hard-coded LOG_FILE and update the log() function so the log path is
configurable and created safely: make LOG_FILE a configurable variable (e.g.,
default to a project ".claude" dir or the user state dir) and resolve it at
module init, ensure the parent directory exists (create it if missing) with
proper permissions before any writes, and continue to use the existing
log(message) function to append timestamped entries; refer to LOG_FILE and log()
in retrieve_guidelines.py when making these changes.


log("Script started")


def find_guidelines_file():
"""Find the guidelines file in common locations."""
locations = [
os.environ.get("GUIDELINES_FILE"),
# Project root from Claude Code
os.path.join(os.environ.get("CLAUDE_PROJECT_ROOT", ""), ".claude/guidelines.json"),
# Current working directory
".claude/guidelines.json",
# Plugin-relative path (fallback)
str(Path(__file__).parent.parent / "guidelines.json"),
]
Comment on lines +22 to +32

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Align guideline search paths with save_guidelines.py to avoid “write-but-never-read”.

If CLAUDE_PROJECT_ROOT is unset and .claude/ doesn’t exist, save_guidelines.py writes guidelines.json in the cwd. retrieve_guidelines.py never searches that location, so saved guidelines won’t be retrieved.

🛠️ Suggested fix (include cwd guidelines.json)
 def find_guidelines_file():
     """Find the guidelines file in common locations."""
     locations = [
         os.environ.get("GUIDELINES_FILE"),
         # Project root from Claude Code
         os.path.join(os.environ.get("CLAUDE_PROJECT_ROOT", ""), ".claude/guidelines.json"),
         # Current working directory
         ".claude/guidelines.json",
+        # CWD fallback (aligned with save_guidelines.py default)
+        "guidelines.json",
         # Plugin-relative path (fallback)
         str(Path(__file__).parent.parent / "guidelines.json"),
     ]
🤖 Prompt for AI Agents
In `@plugins/kaizen/scripts/retrieve_guidelines.py` around lines 22 - 32, The
find_guidelines_file function currently searches .claude/guidelines.json and
plugin-relative paths but misses the plain guidelines.json that
save_guidelines.py writes to the current working directory when
CLAUDE_PROJECT_ROOT is unset; update the locations list in find_guidelines_file
to include the cwd-root file (e.g., Path.cwd()/ "guidelines.json" or
os.path.join(os.getcwd(), "guidelines.json")) so retrieve_guidelines.py aligns
with save_guidelines.py and will find guidelines saved to the working directory.

for loc in locations:
if loc and Path(loc).exists():
return Path(loc)
return None


def load_guidelines():
"""Load guidelines from the guidelines file."""
guidelines_file = find_guidelines_file()
if not guidelines_file:
return []
try:
with open(guidelines_file) as f:
data = json.load(f)
return data.get("guidelines", [])
except (json.JSONDecodeError, IOError):
return []


def format_guidelines(guidelines):
"""Format all guidelines for Claude to review."""
header = """## Guidelines for this task

Review these guidelines and apply any relevant ones:

"""
items = []
for g in guidelines:
content = g.get('content')
if not content:
continue
item = f"- **[{g.get('category', 'general')}]** {content}"
if g.get('rationale'):
item += f"\n - _Rationale: {g['rationale']}_"
if g.get('trigger'):
item += f"\n - _When: {g['trigger']}_"
items.append(item)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return header + "\n".join(items)


def main():
# Read input from stdin (hook provides JSON with prompt)
try:
input_data = json.load(sys.stdin)
log(f"Received input with keys: {list(input_data.keys())}")
except json.JSONDecodeError as e:
log(f"Failed to parse JSON input: {e}")
return

# Load all guidelines
guidelines_file = find_guidelines_file()
log(f"Guidelines file: {guidelines_file}")

guidelines = load_guidelines()
if not guidelines:
log("No guidelines found")
return

log(f"Loaded {len(guidelines)} guidelines")

# Output all guidelines - Claude will filter for relevance
output = format_guidelines(guidelines)
print(output)
log(f"Output {len(output)} chars to stdout")


if __name__ == "__main__":
main()
Loading