claude-self-reflect 7.1.8 → 7.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,244 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # Ralph Memory Integration - Hook Installation Script
4
+ # =============================================================================
5
+ # Installs Ralph memory hooks into Claude Code's hook system.
6
+ #
7
+ # Usage:
8
+ # ./install_hooks.sh # Install hooks
9
+ # ./install_hooks.sh --check # Check installation status
10
+ # ./install_hooks.sh --remove # Remove hooks
11
+ # =============================================================================
12
+
13
+ set -e
14
+
15
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
17
+ CLAUDE_HOOKS_DIR="$HOME/.claude/hooks"
18
+ CLAUDE_SETTINGS="$HOME/.claude/settings.json"
19
+
20
+ # Colors
21
+ RED='\033[0;31m'
22
+ GREEN='\033[0;32m'
23
+ YELLOW='\033[1;33m'
24
+ NC='\033[0m'
25
+
26
+ log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
27
+ log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
28
+ log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
29
+
30
+ check_installation() {
31
+ echo "=========================================="
32
+ echo "Ralph Memory Integration - Status Check"
33
+ echo "=========================================="
34
+
35
+ local all_ok=true
36
+
37
+ # Check hooks directory
38
+ if [ -d "$CLAUDE_HOOKS_DIR" ]; then
39
+ log_info "✓ Hooks directory exists: $CLAUDE_HOOKS_DIR"
40
+ else
41
+ log_warn "○ Hooks directory not found: $CLAUDE_HOOKS_DIR"
42
+ all_ok=false
43
+ fi
44
+
45
+ # Check for our hooks symlinks or copies
46
+ if [ -f "$CLAUDE_HOOKS_DIR/ralph-session-start.py" ] || [ -L "$CLAUDE_HOOKS_DIR/ralph-session-start.py" ]; then
47
+ log_info "✓ SessionStart hook installed"
48
+ else
49
+ log_warn "○ SessionStart hook not installed"
50
+ all_ok=false
51
+ fi
52
+
53
+ if [ -f "$CLAUDE_HOOKS_DIR/ralph-session-end.py" ] || [ -L "$CLAUDE_HOOKS_DIR/ralph-session-end.py" ]; then
54
+ log_info "✓ SessionEnd hook installed"
55
+ else
56
+ log_warn "○ SessionEnd hook not installed"
57
+ all_ok=false
58
+ fi
59
+
60
+ # Check settings.json for hook configuration
61
+ if [ -f "$CLAUDE_SETTINGS" ]; then
62
+ if grep -q "ralph" "$CLAUDE_SETTINGS" 2>/dev/null; then
63
+ log_info "✓ Settings.json contains Ralph configuration"
64
+ else
65
+ log_warn "○ Settings.json does not contain Ralph configuration"
66
+ all_ok=false
67
+ fi
68
+ else
69
+ log_warn "○ Settings.json not found"
70
+ all_ok=false
71
+ fi
72
+
73
+ # Check source hooks exist
74
+ if [ -f "$PROJECT_ROOT/src/runtime/hooks/session_start_hook.py" ]; then
75
+ log_info "✓ Source hooks available"
76
+ else
77
+ log_error "✗ Source hooks missing!"
78
+ all_ok=false
79
+ fi
80
+
81
+ echo ""
82
+ if $all_ok; then
83
+ log_info "All hooks properly installed"
84
+ else
85
+ log_warn "Some hooks are missing. Run: $0 to install"
86
+ fi
87
+ }
88
+
89
+ install_hooks() {
90
+ echo "=========================================="
91
+ echo "Ralph Memory Integration - Installing"
92
+ echo "=========================================="
93
+
94
+ # Create hooks directory if needed
95
+ mkdir -p "$CLAUDE_HOOKS_DIR"
96
+ log_info "Created hooks directory: $CLAUDE_HOOKS_DIR"
97
+
98
+ # Create symlinks to our hooks
99
+ ln -sf "$PROJECT_ROOT/src/runtime/hooks/session_start_hook.py" "$CLAUDE_HOOKS_DIR/ralph-session-start.py"
100
+ ln -sf "$PROJECT_ROOT/src/runtime/hooks/session_end_hook.py" "$CLAUDE_HOOKS_DIR/ralph-session-end.py"
101
+ log_info "Created hook symlinks"
102
+
103
+ # Create or update settings.json with hook configuration
104
+ if [ ! -f "$CLAUDE_SETTINGS" ]; then
105
+ echo '{}' > "$CLAUDE_SETTINGS"
106
+ fi
107
+
108
+ # Use Python to safely merge hook configuration
109
+ python3 << PYTHON
110
+ import json
111
+ from pathlib import Path
112
+
113
+ settings_path = Path("$CLAUDE_SETTINGS")
114
+ project_root = "$PROJECT_ROOT"
115
+
116
+ # Load existing settings
117
+ try:
118
+ settings = json.loads(settings_path.read_text())
119
+ except:
120
+ settings = {}
121
+
122
+ # Ensure hooks section exists
123
+ if 'hooks' not in settings:
124
+ settings['hooks'] = {}
125
+
126
+ # Add Ralph hooks if not present
127
+ ralph_hooks = {
128
+ "SessionStart": [{
129
+ "matcher": "startup|resume",
130
+ "hooks": [{
131
+ "type": "command",
132
+ "command": f"{project_root}/venv/bin/python3 {project_root}/src/runtime/hooks/session_start_hook.py 2>/dev/null || true"
133
+ }]
134
+ }],
135
+ "SessionEnd": [{
136
+ "hooks": [{
137
+ "type": "command",
138
+ "command": f"{project_root}/venv/bin/python3 {project_root}/src/runtime/hooks/session_end_hook.py 2>/dev/null || true"
139
+ }]
140
+ }]
141
+ }
142
+
143
+ # Merge (don't overwrite existing hooks)
144
+ for hook_type, hook_configs in ralph_hooks.items():
145
+ if hook_type not in settings['hooks']:
146
+ settings['hooks'][hook_type] = []
147
+
148
+ # Check if Ralph hook already exists
149
+ existing = settings['hooks'][hook_type]
150
+ ralph_cmd = f"{project_root}/src/runtime/hooks"
151
+
152
+ has_ralph = any(
153
+ ralph_cmd in str(h.get('hooks', []))
154
+ for h in existing
155
+ )
156
+
157
+ if not has_ralph:
158
+ settings['hooks'][hook_type].extend(hook_configs)
159
+ print(f" Added {hook_type} hook")
160
+ else:
161
+ print(f" {hook_type} hook already exists")
162
+
163
+ # Write back
164
+ settings_path.write_text(json.dumps(settings, indent=2))
165
+ print("Settings updated successfully")
166
+ PYTHON
167
+
168
+ log_info "Hook configuration added to settings.json"
169
+
170
+ echo ""
171
+ log_info "Installation complete!"
172
+ echo ""
173
+ echo "To verify: $0 --check"
174
+ echo ""
175
+ echo "NOTE: The hooks will activate when:"
176
+ echo " 1. You start a Ralph loop with /ralph-wiggum:ralph-loop"
177
+ echo " 2. The hooks detect .claude/ralph-loop.local.md"
178
+ echo " 3. Session events (start/end) trigger memory operations"
179
+ }
180
+
181
+ remove_hooks() {
182
+ echo "=========================================="
183
+ echo "Ralph Memory Integration - Removing"
184
+ echo "=========================================="
185
+
186
+ # Remove symlinks
187
+ rm -f "$CLAUDE_HOOKS_DIR/ralph-session-start.py"
188
+ rm -f "$CLAUDE_HOOKS_DIR/ralph-session-end.py"
189
+ log_info "Removed hook symlinks"
190
+
191
+ # Remove from settings.json
192
+ if [ -f "$CLAUDE_SETTINGS" ]; then
193
+ python3 << PYTHON
194
+ import json
195
+ from pathlib import Path
196
+
197
+ settings_path = Path("$CLAUDE_SETTINGS")
198
+ project_root = "$PROJECT_ROOT"
199
+
200
+ try:
201
+ settings = json.loads(settings_path.read_text())
202
+ except:
203
+ exit(0)
204
+
205
+ if 'hooks' not in settings:
206
+ exit(0)
207
+
208
+ # Remove Ralph-related hooks
209
+ for hook_type in ['SessionStart', 'SessionEnd']:
210
+ if hook_type in settings['hooks']:
211
+ settings['hooks'][hook_type] = [
212
+ h for h in settings['hooks'][hook_type]
213
+ if project_root not in str(h)
214
+ ]
215
+ if not settings['hooks'][hook_type]:
216
+ del settings['hooks'][hook_type]
217
+
218
+ if not settings['hooks']:
219
+ del settings['hooks']
220
+
221
+ settings_path.write_text(json.dumps(settings, indent=2))
222
+ print("Settings updated")
223
+ PYTHON
224
+ log_info "Removed hook configuration from settings.json"
225
+ fi
226
+
227
+ echo ""
228
+ log_info "Removal complete!"
229
+ }
230
+
231
+ # =============================================================================
232
+ # MAIN
233
+ # =============================================================================
234
+ case "${1:-}" in
235
+ --check)
236
+ check_installation
237
+ ;;
238
+ --remove)
239
+ remove_hooks
240
+ ;;
241
+ *)
242
+ install_hooks
243
+ ;;
244
+ esac
@@ -0,0 +1,195 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # Ralph Memory Integration - Test Runner with Automatic Rollback
4
+ # =============================================================================
5
+ # Runs all integration tests and automatically rolls back if any fail.
6
+ #
7
+ # Usage:
8
+ # ./test_with_rollback.sh <backup_directory>
9
+ #
10
+ # Example:
11
+ # ./test_with_rollback.sh ~/.claude-self-reflect/backups/20260104_120000_pre_ralph_memory
12
+ # =============================================================================
13
+
14
+ set -e # Exit on any error
15
+
16
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
18
+ BACKUP_DIR="$1"
19
+
20
+ # Colors
21
+ RED='\033[0;31m'
22
+ GREEN='\033[0;32m'
23
+ YELLOW='\033[1;33m'
24
+ NC='\033[0m'
25
+
26
+ log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
27
+ log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
28
+ log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
29
+
30
+ # Validate backup directory
31
+ if [ -z "$BACKUP_DIR" ]; then
32
+ echo "Usage: $0 <backup_directory>"
33
+ echo ""
34
+ echo "Create a backup first:"
35
+ echo " ./backup_and_restore.sh backup"
36
+ exit 1
37
+ fi
38
+
39
+ if [ ! -d "$BACKUP_DIR" ]; then
40
+ log_error "Backup directory not found: $BACKUP_DIR"
41
+ exit 1
42
+ fi
43
+
44
+ if [ ! -f "$BACKUP_DIR/manifest.json" ]; then
45
+ log_error "Invalid backup: manifest.json not found"
46
+ exit 1
47
+ fi
48
+
49
+ echo "=========================================="
50
+ echo "Ralph Memory Integration Test Runner"
51
+ echo "=========================================="
52
+ echo "Backup: $BACKUP_DIR"
53
+ echo "Project: $PROJECT_ROOT"
54
+ echo "=========================================="
55
+ echo ""
56
+
57
+ cd "$PROJECT_ROOT"
58
+
59
+ # Activate virtual environment if it exists
60
+ if [ -d "$PROJECT_ROOT/venv" ]; then
61
+ source "$PROJECT_ROOT/venv/bin/activate"
62
+ log_info "Using venv Python: $(which python)"
63
+ fi
64
+
65
+ # =============================================================================
66
+ # ROLLBACK FUNCTION
67
+ # =============================================================================
68
+ rollback() {
69
+ echo ""
70
+ echo -e "${RED}=========================================="
71
+ echo "TESTS FAILED - INITIATING AUTOMATIC ROLLBACK"
72
+ echo "==========================================${NC}"
73
+ echo ""
74
+
75
+ # Use the backup_and_restore script for actual restore
76
+ "$SCRIPT_DIR/backup_and_restore.sh" restore "$BACKUP_DIR" << EOF
77
+ yes
78
+ EOF
79
+
80
+ echo ""
81
+ log_error "ROLLBACK COMPLETE - System restored to pre-implementation state"
82
+ exit 1
83
+ }
84
+
85
+ # Trap errors and rollback
86
+ trap rollback ERR
87
+
88
+ # =============================================================================
89
+ # RUN TESTS
90
+ # =============================================================================
91
+
92
+ log_info "Running tests with automatic rollback on failure..."
93
+ echo ""
94
+
95
+ # Test 1: Check directory structure
96
+ log_info "Test 1: Checking directory structure..."
97
+ [ -d "src/runtime/hooks" ] || { log_error "Missing: src/runtime/hooks/"; exit 1; }
98
+ echo " ✓ src/runtime/hooks/ exists"
99
+
100
+ # Test 2: Check ralph_state module
101
+ log_info "Test 2: Testing ralph_state module..."
102
+ python3 -c "
103
+ from src.runtime.hooks.ralph_state import RalphState, load_state, save_state, is_ralph_session
104
+ import tempfile
105
+ from pathlib import Path
106
+
107
+ # Test create
108
+ state = RalphState.create_new('Test task', 'Test complete')
109
+ assert state.task == 'Test task', 'Task mismatch'
110
+ assert state.iteration == 1, 'Iteration should be 1'
111
+ assert state.session_id.startswith('ralph_'), 'Invalid session ID'
112
+
113
+ # Test roundtrip
114
+ with tempfile.TemporaryDirectory() as tmpdir:
115
+ path = Path(tmpdir) / '.ralph_state.md'
116
+ state.failed_approaches = ['Approach A']
117
+ state.learnings = ['Learning 1']
118
+ save_state(state, path)
119
+
120
+ loaded = load_state(path)
121
+ assert loaded.task == state.task, 'Roundtrip failed: task'
122
+ assert 'Approach A' in loaded.failed_approaches, 'Roundtrip failed: approaches'
123
+
124
+ print(' ✓ RalphState module working correctly')
125
+ "
126
+
127
+ # Test 3: SessionStart hook
128
+ log_info "Test 3: Testing session_start_hook..."
129
+ echo '{"session_id": "test123", "source": "startup"}' | timeout 10 python3 src/runtime/hooks/session_start_hook.py 2>&1 || true
130
+ echo " ✓ session_start_hook exits cleanly"
131
+
132
+ # Test 4: SessionEnd hook
133
+ log_info "Test 4: Testing session_end_hook..."
134
+ echo '{"session_id": "test123", "reason": "clear"}' | timeout 10 python3 src/runtime/hooks/session_end_hook.py 2>&1 || true
135
+ echo " ✓ session_end_hook exits cleanly"
136
+
137
+ # Test 5: PreCompact hook enhancement
138
+ log_info "Test 5: Checking precompact-hook.sh enhancement..."
139
+ if grep -q "RALPH MEMORY INTEGRATION" src/runtime/precompact-hook.sh 2>/dev/null; then
140
+ echo " ✓ precompact-hook.sh contains Ralph integration"
141
+ else
142
+ log_warn " ○ precompact-hook.sh not yet enhanced (may be pending)"
143
+ fi
144
+
145
+ # Test 6: Qdrant connectivity
146
+ log_info "Test 6: Checking Qdrant connectivity..."
147
+ curl -sf http://localhost:6333/collections > /dev/null && echo " ✓ Qdrant accessible" || log_warn " ○ Qdrant not accessible (may not be required)"
148
+
149
+ # Test 7: Run pytest unit tests
150
+ log_info "Test 7: Running pytest unit tests..."
151
+ if [ -f "tests/ralph/test_ralph_integration.py" ]; then
152
+ python -m pytest tests/ralph/test_ralph_integration.py -v --tb=short -k "not Compaction" 2>&1 || { log_error "Pytest unit tests failed"; exit 1; }
153
+ echo " ✓ Pytest unit tests passed"
154
+ else
155
+ log_warn " ○ Integration tests not yet created"
156
+ fi
157
+
158
+ # Test 8: Run CRITICAL compaction scenario tests (requires CSR running)
159
+ log_info "Test 8: Running CRITICAL compaction scenario tests..."
160
+ if curl -sf http://localhost:6333/collections > /dev/null 2>&1; then
161
+ # CSR is available, run compaction tests
162
+ python -m pytest tests/ralph/test_ralph_integration.py -v --tb=short -k "Compaction" 2>&1 || {
163
+ log_error "CRITICAL: Compaction scenario tests failed!"
164
+ log_error "These tests verify the core value proposition:"
165
+ log_error " - PreCompact backs up state to CSR"
166
+ log_error " - State recovery after compaction"
167
+ log_error " - Cross-session memory works"
168
+ exit 1
169
+ }
170
+ echo " ✓ Compaction scenario tests passed"
171
+ else
172
+ log_warn " ○ CSR not available, skipping compaction tests"
173
+ log_warn " (Start Qdrant to run: docker start claude-reflection-qdrant)"
174
+ fi
175
+
176
+ # Test 9: Docker services still healthy
177
+ log_info "Test 9: Checking Docker services..."
178
+ docker ps --filter "name=claude-reflection-qdrant" --format "{{.Status}}" | grep -q "Up" && echo " ✓ Qdrant container healthy" || log_warn " ○ Qdrant container not running"
179
+ docker ps --filter "name=claude-reflection-batch-watcher" --format "{{.Status}}" | grep -q "Up" && echo " ✓ Batch watcher healthy" || log_warn " ○ Batch watcher not running"
180
+
181
+ echo ""
182
+ echo -e "${GREEN}=========================================="
183
+ echo "ALL TESTS PASSED"
184
+ echo "==========================================${NC}"
185
+ echo ""
186
+ echo "The implementation is verified and safe."
187
+ echo ""
188
+ echo "Next steps:"
189
+ echo " 1. Review changes: git diff main"
190
+ echo " 2. Commit: git add -A && git commit -m 'feat: add Ralph memory integration hooks'"
191
+ echo " 3. Push: git push -u origin feat/ralph-csr-integration"
192
+ echo " 4. Create PR: gh pr create"
193
+ echo ""
194
+ echo "Backup retained at: $BACKUP_DIR"
195
+ echo "(You can delete it after merge: rm -rf $BACKUP_DIR)"
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Ralph Iteration Hook - Fires at EACH iteration boundary via Stop hook.
4
+
5
+ This is the ONLY viable hook for iteration-level memory because:
6
+ - Stop hook fires after EACH Claude response (iteration boundary)
7
+ - SessionStart/SessionEnd fire once per terminal session (useless)
8
+
9
+ When triggered:
10
+ 1. Store learnings from THIS iteration (with session_id + iteration tag)
11
+ 2. Retrieve learnings from PREVIOUS iterations of same session
12
+ 3. Output context for next iteration
13
+
14
+ Input (stdin): JSON with transcript, etc.
15
+ Output (stdout): Context message injected into next iteration
16
+ """
17
+
18
+ import sys
19
+ import json
20
+ import logging
21
+ import re
22
+ from pathlib import Path
23
+ from datetime import datetime
24
+
25
+ # Add project root to path
26
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
27
+
28
+ from src.runtime.hooks.ralph_state import (
29
+ is_ralph_session,
30
+ load_ralph_session_state,
31
+ )
32
+
33
+ logging.basicConfig(level=logging.INFO)
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ def get_project_root() -> Path:
38
+ return Path(__file__).parent.parent.parent.parent
39
+
40
+
41
+ def store_iteration_learnings(state, iteration: int) -> bool:
42
+ """Store learnings from current iteration to CSR."""
43
+ try:
44
+ project_root = get_project_root()
45
+ mcp_server_path = project_root / "mcp-server" / "src"
46
+ if str(mcp_server_path) not in sys.path:
47
+ sys.path.insert(0, str(mcp_server_path))
48
+ from standalone_client import CSRStandaloneClient
49
+ client = CSRStandaloneClient()
50
+
51
+ # Get working directory (project name)
52
+ cwd = Path.cwd()
53
+ # SECURITY: Sanitize project name to prevent injection
54
+ project_name = re.sub(r'[^a-zA-Z0-9_-]', '_', cwd.name)
55
+
56
+ # Get learnings (may be empty)
57
+ learnings = getattr(state, 'learnings', [])
58
+
59
+ # Create iteration-specific content (store even if no learnings to track hook fired)
60
+ content = f"""# Iteration {iteration} (Session: {state.session_id})
61
+
62
+ ## Project
63
+ {project_name} ({cwd})
64
+
65
+ ## Task
66
+ {state.task[:200] if state.task else '(no task)'}
67
+
68
+ ## Learnings
69
+ {chr(10).join(f'- {l}' for l in learnings) if learnings else '(none this iteration)'}
70
+
71
+ ## Current Approach
72
+ {state.current_approach or '(not set)'}
73
+
74
+ ## Files Modified
75
+ {chr(10).join(f'- {f}' for f in state.files_modified) if state.files_modified else '(none)'}
76
+ """
77
+
78
+ # Store with iteration-specific tags including project
79
+ tags = [
80
+ "__csr_hook_auto__", # Hook signature
81
+ f"project_{project_name}",
82
+ f"session_{state.session_id}",
83
+ f"iteration_{iteration}",
84
+ "ralph_iteration"
85
+ ]
86
+
87
+ client.store_reflection(
88
+ content=content,
89
+ tags=tags,
90
+ collection="csr_hook_sessions_local"
91
+ )
92
+
93
+ logger.info(f"Stored iteration {iteration} for {project_name} session {state.session_id}")
94
+ return True
95
+
96
+ except Exception as e:
97
+ logger.error(f"Error storing iteration learnings: {e}")
98
+ return False
99
+
100
+
101
+ def retrieve_iteration_learnings(session_id: str, current_iteration: int) -> list:
102
+ """Retrieve learnings from PREVIOUS iterations of this session."""
103
+ try:
104
+ project_root = get_project_root()
105
+ mcp_server_path = project_root / "mcp-server" / "src"
106
+ if str(mcp_server_path) not in sys.path:
107
+ sys.path.insert(0, str(mcp_server_path))
108
+ from standalone_client import CSRStandaloneClient
109
+ client = CSRStandaloneClient()
110
+
111
+ # Get learnings from this session (use hook collection)
112
+ learnings = client.get_session_learnings(
113
+ session_id,
114
+ limit=20,
115
+ collection="csr_hook_sessions_local"
116
+ )
117
+
118
+ # Filter to only previous iterations
119
+ previous = [
120
+ l for l in learnings
121
+ if any(f"iteration_{i}" in l.get('tags', [])
122
+ for i in range(1, current_iteration))
123
+ ]
124
+
125
+ return previous
126
+
127
+ except Exception as e:
128
+ logger.error(f"Error retrieving iteration learnings: {e}")
129
+ return []
130
+
131
+
132
+ def format_iteration_context(learnings: list, current_iteration: int) -> str:
133
+ """Format previous iteration learnings for injection."""
134
+ if not learnings:
135
+ return ""
136
+
137
+ output = [f"# Previous Iteration Learnings (for Iteration {current_iteration})"]
138
+ output.append("")
139
+
140
+ for l in learnings[:5]: # Max 5 previous iterations
141
+ content = l.get('content', '')[:500]
142
+ tags = l.get('tags', [])
143
+ iter_tag = [t for t in tags if t.startswith('iteration_')]
144
+ iter_num = iter_tag[0].split('_')[1] if iter_tag else '?'
145
+ output.append(f"## From Iteration {iter_num}")
146
+ output.append(content)
147
+ output.append("")
148
+
149
+ return "\n".join(output)
150
+
151
+
152
+ def main():
153
+ """Main hook entry point - fires at each iteration boundary."""
154
+ # Read hook input (required by Claude Code hook framework, but we use Ralph state file instead)
155
+ # The stdin contains transcript data, but Ralph state file is the authoritative source
156
+ try:
157
+ _ = json.load(sys.stdin) # Consume stdin as required by hook protocol
158
+ except (json.JSONDecodeError, EOFError):
159
+ pass # No input is fine - we get state from .ralph_state.md
160
+
161
+ # Check if Ralph loop active
162
+ if not is_ralph_session():
163
+ logger.info("No Ralph session - iteration hook skipped")
164
+ sys.exit(0)
165
+
166
+ # Load state
167
+ state = load_ralph_session_state()
168
+ if not state:
169
+ logger.warning("Could not load Ralph state")
170
+ sys.exit(0)
171
+
172
+ iteration = state.iteration
173
+ session_id = state.session_id
174
+
175
+ logger.info(f"Iteration hook: session={session_id}, iteration={iteration}")
176
+
177
+ # 1. Store learnings from THIS iteration
178
+ store_iteration_learnings(state, iteration)
179
+
180
+ # 2. Retrieve learnings from PREVIOUS iterations
181
+ previous_learnings = retrieve_iteration_learnings(session_id, iteration)
182
+
183
+ # 3. Output context for NEXT iteration
184
+ if previous_learnings:
185
+ context = format_iteration_context(previous_learnings, iteration + 1)
186
+ # Write to file for Claude to read
187
+ context_path = Path('.ralph_iteration_context.md')
188
+ context_path.write_text(context)
189
+ print(f"# Loaded {len(previous_learnings)} learnings from previous iterations")
190
+ print(f"# See .ralph_iteration_context.md for details")
191
+ else:
192
+ logger.info("No previous iteration learnings found")
193
+
194
+
195
+ if __name__ == "__main__":
196
+ main()
@@ -119,6 +119,11 @@ class RalphState:
119
119
  score += 10
120
120
  self.exit_confidence = min(100, score)
121
121
 
122
+ def increment_iteration(self) -> None:
123
+ """Increment iteration count and update timestamp."""
124
+ self.iteration += 1
125
+ self.updated_at = datetime.now().isoformat()
126
+
122
127
  def to_markdown(self) -> str:
123
128
  """Convert state to markdown format for .ralph_state.md"""
124
129
  # Format error signatures for display
@@ -141,7 +141,10 @@ Promise Met: {state.completion_promise_met}
141
141
  """
142
142
 
143
143
  # Store with outcome-aware tags
144
+ # IMPORTANT: __csr_hook_auto__ is a distinct signature that identifies
145
+ # hook-stored reflections vs manually-stored ones during development
144
146
  tags = [
147
+ "__csr_hook_auto__", # Hook signature - don't manually use this tag!
145
148
  "ralph_session",
146
149
  f"session_{state.session_id}",
147
150
  f"outcome_{outcome.lower()}",
@@ -165,7 +168,12 @@ Promise Met: {state.completion_promise_met}
165
168
 
166
169
  # Note: CSR store_reflection may not support metadata yet,
167
170
  # but we include the rich info in the narrative for searchability
168
- client.store_reflection(content=narrative, tags=tags)
171
+ # Use hook-specific collection so random agents don't pollute it
172
+ client.store_reflection(
173
+ content=narrative,
174
+ tags=tags,
175
+ collection="csr_hook_sessions_local" # Separate from user reflections
176
+ )
169
177
 
170
178
  logger.info(f"Stored session narrative: {outcome}, {state.iteration} iterations, confidence={exit_confidence}%")
171
179
 
@@ -179,7 +187,8 @@ Iterations: {state.iteration}
179
187
  """
180
188
  client.store_reflection(
181
189
  content=success_summary,
182
- tags=["ralph_success", "winning_strategy", f"work_type_{work_type.lower()}"]
190
+ tags=["__csr_hook_auto__", "ralph_success", "winning_strategy", f"work_type_{work_type.lower()}"],
191
+ collection="csr_hook_sessions_local"
183
192
  )
184
193
 
185
194
  return True