@trac3er/oh-my-god 2.0.0-beta.2 → 2.1.0-alpha

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.
Files changed (47) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/OMG-setup.sh +33 -0
  4. package/README.md +43 -0
  5. package/claude_experimental/__init__.py +27 -0
  6. package/claude_experimental/_compat.py +12 -0
  7. package/claude_experimental/_degradation.py +210 -0
  8. package/claude_experimental/_flags.py +35 -0
  9. package/claude_experimental/_lifecycle.py +122 -0
  10. package/claude_experimental/integration/__init__.py +16 -0
  11. package/claude_experimental/integration/_placeholder.py +7 -0
  12. package/claude_experimental/integration/autotuner.py +182 -0
  13. package/claude_experimental/integration/checkpoints.py +327 -0
  14. package/claude_experimental/integration/experiments.py +302 -0
  15. package/claude_experimental/integration/openapi_gen.py +428 -0
  16. package/claude_experimental/integration/streaming.py +131 -0
  17. package/claude_experimental/integration/telemetry.py +287 -0
  18. package/claude_experimental/memory/__init__.py +16 -0
  19. package/claude_experimental/memory/_placeholder.py +7 -0
  20. package/claude_experimental/memory/api.py +434 -0
  21. package/claude_experimental/memory/augmented_generation.py +142 -0
  22. package/claude_experimental/memory/episodic.py +190 -0
  23. package/claude_experimental/memory/failure_learning.py +183 -0
  24. package/claude_experimental/memory/migrate.py +169 -0
  25. package/claude_experimental/memory/procedural.py +212 -0
  26. package/claude_experimental/memory/semantic.py +376 -0
  27. package/claude_experimental/memory/store.py +310 -0
  28. package/claude_experimental/parallel/__init__.py +17 -0
  29. package/claude_experimental/parallel/_placeholder.py +7 -0
  30. package/claude_experimental/parallel/aggregation.py +131 -0
  31. package/claude_experimental/parallel/api.py +244 -0
  32. package/claude_experimental/parallel/executor.py +110 -0
  33. package/claude_experimental/parallel/ralph_bridge.py +164 -0
  34. package/claude_experimental/parallel/sandbox.py +314 -0
  35. package/claude_experimental/parallel/scaling.py +171 -0
  36. package/claude_experimental/parallel/ultraworker.py +285 -0
  37. package/claude_experimental/patterns/__init__.py +16 -0
  38. package/claude_experimental/patterns/_placeholder.py +7 -0
  39. package/claude_experimental/patterns/antipatterns.py +454 -0
  40. package/claude_experimental/patterns/api.py +196 -0
  41. package/claude_experimental/patterns/extractor.py +163 -0
  42. package/claude_experimental/patterns/mining.py +219 -0
  43. package/claude_experimental/patterns/refactoring.py +234 -0
  44. package/hooks/_common.py +3 -1
  45. package/package.json +1 -1
  46. package/runtime/subagent_dispatcher.py +109 -9
  47. package/settings.json +6 -1
@@ -0,0 +1,190 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib import import_module
4
+ import json
5
+ from typing import cast
6
+
7
+ from claude_experimental.memory.store import MemoryStore
8
+
9
+ _EVENT_TYPES = {"success", "failure", "decision", "discovery"}
10
+
11
+
12
+ class EpisodicMemory:
13
+ def __init__(self, store: MemoryStore | None = None):
14
+ self.store: MemoryStore = store or MemoryStore()
15
+
16
+ def record(
17
+ self,
18
+ event_type: str,
19
+ context: object,
20
+ outcome: object,
21
+ session_id: str | None = None,
22
+ metadata: dict[str, object] | None = None,
23
+ ) -> int:
24
+ _require_memory_enabled()
25
+
26
+ normalized_event_type = event_type.strip().lower()
27
+ if normalized_event_type not in _EVENT_TYPES:
28
+ allowed = ", ".join(sorted(_EVENT_TYPES))
29
+ raise ValueError(f"Invalid event_type '{event_type}'. Allowed values: {allowed}")
30
+
31
+ importance = self._importance_for(normalized_event_type, outcome, metadata)
32
+ payload = {
33
+ "event_type": normalized_event_type,
34
+ "session_id": session_id,
35
+ "context": context,
36
+ "outcome": outcome,
37
+ }
38
+ content = self._to_text(payload)
39
+
40
+ store_metadata: dict[str, object] = {
41
+ "event_type": normalized_event_type,
42
+ "session_id": session_id,
43
+ "context": context,
44
+ "outcome": outcome,
45
+ }
46
+ if metadata:
47
+ store_metadata.update(metadata)
48
+
49
+ return self.store.save(
50
+ content=content,
51
+ memory_type="episodic",
52
+ importance=importance,
53
+ metadata=store_metadata,
54
+ )
55
+
56
+ def recall(
57
+ self,
58
+ query: str,
59
+ limit: int = 5,
60
+ temperature: float = 0.3,
61
+ event_type_filter: str | None = None,
62
+ ) -> list[dict[str, object]]:
63
+ _require_memory_enabled()
64
+
65
+ clamped_temperature = max(0.0, min(1.0, temperature))
66
+ min_score = 1.0 - clamped_temperature
67
+ normalized_filter = event_type_filter.strip().lower() if event_type_filter else None
68
+ if normalized_filter and normalized_filter not in _EVENT_TYPES:
69
+ allowed = ", ".join(sorted(_EVENT_TYPES))
70
+ raise ValueError(
71
+ f"Invalid event_type_filter '{event_type_filter}'. Allowed values: {allowed}"
72
+ )
73
+
74
+ base_results = self.store.search(
75
+ query=query,
76
+ limit=max(limit, 1),
77
+ memory_type="episodic",
78
+ )
79
+
80
+ filtered: list[dict[str, object]] = []
81
+ total = len(base_results)
82
+ for index, item in enumerate(base_results):
83
+ memory = dict(item)
84
+ metadata_raw = memory.get("metadata")
85
+ if isinstance(metadata_raw, str):
86
+ parsed_json: object
87
+ try:
88
+ parsed_json = cast(object, json.loads(metadata_raw))
89
+ except json.JSONDecodeError:
90
+ parsed_json = cast(object, {})
91
+ metadata = _coerce_object_dict(parsed_json)
92
+ else:
93
+ metadata = _coerce_object_dict(metadata_raw)
94
+
95
+ event_type = str(metadata.get("event_type", "")).strip().lower()
96
+ if normalized_filter and event_type != normalized_filter:
97
+ continue
98
+
99
+ relevance = self._relevance(memory, index, total)
100
+ memory["score"] = relevance
101
+ if relevance >= min_score:
102
+ filtered.append(memory)
103
+ if len(filtered) >= limit:
104
+ break
105
+
106
+ return filtered
107
+
108
+ @staticmethod
109
+ def _importance_for(
110
+ event_type: str,
111
+ outcome: object,
112
+ metadata: dict[str, object] | None,
113
+ ) -> float:
114
+ if event_type == "success":
115
+ return 0.7
116
+ if event_type == "failure" and EpisodicMemory._has_lessons(outcome, metadata):
117
+ return 0.8
118
+ return 0.3
119
+
120
+ @staticmethod
121
+ def _has_lessons(outcome: object, metadata: dict[str, object] | None) -> bool:
122
+ if metadata:
123
+ if _contains_lesson_signal(metadata.get("lessons")):
124
+ return True
125
+
126
+ outcome_dict = _coerce_object_dict(outcome)
127
+ if _contains_lesson_signal(outcome_dict.get("lessons")):
128
+ return True
129
+
130
+ outcome_text = EpisodicMemory._to_text(outcome).lower()
131
+ return "lesson" in outcome_text or "learned" in outcome_text
132
+
133
+ @staticmethod
134
+ def _to_text(value: object) -> str:
135
+ if isinstance(value, str):
136
+ return value
137
+ return json.dumps(value, sort_keys=True, default=str)
138
+
139
+ @staticmethod
140
+ def _relevance(memory: dict[str, object], index: int, total: int) -> float:
141
+ if total <= 1:
142
+ rank_relevance = 1.0
143
+ else:
144
+ rank_relevance = max(0.0, min(1.0, 1.0 - (index / (total - 1))))
145
+
146
+ score = memory.get("score")
147
+ if isinstance(score, (int, float)):
148
+ return max(0.0, min(1.0, float(score)))
149
+
150
+ return rank_relevance
151
+
152
+
153
+ def _require_memory_enabled() -> None:
154
+ memory_module = import_module("claude_experimental.memory")
155
+ require_enabled = getattr(memory_module, "_require_enabled", None)
156
+ if callable(require_enabled):
157
+ _ = require_enabled()
158
+ return
159
+ raise RuntimeError("claude_experimental.memory._require_enabled is unavailable")
160
+
161
+
162
+ def _coerce_object_dict(value: object) -> dict[str, object]:
163
+ if not isinstance(value, dict):
164
+ return {}
165
+ value_dict = cast(dict[object, object], value)
166
+ return {str(key): item for key, item in value_dict.items()}
167
+
168
+
169
+ def _contains_lesson_signal(value: object) -> bool:
170
+ if isinstance(value, str):
171
+ return bool(value.strip())
172
+ if isinstance(value, list):
173
+ for item in cast(list[object], value):
174
+ if _contains_lesson_signal(item):
175
+ return True
176
+ return False
177
+ if isinstance(value, tuple):
178
+ for item in cast(tuple[object, ...], value):
179
+ if _contains_lesson_signal(item):
180
+ return True
181
+ return False
182
+ if isinstance(value, set):
183
+ for item in cast(set[object], value):
184
+ if _contains_lesson_signal(item):
185
+ return True
186
+ return False
187
+ if isinstance(value, dict):
188
+ value_dict = cast(dict[object, object], value)
189
+ return _contains_lesson_signal(list(value_dict.values()))
190
+ return False
@@ -0,0 +1,183 @@
1
+ """FailureLearner — records agent failures and learns patterns to warn on similar future contexts."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import os
6
+ import re
7
+ from importlib import import_module
8
+ from typing import cast
9
+
10
+ from claude_experimental.memory.episodic import EpisodicMemory
11
+ from claude_experimental.memory.store import MemoryStore
12
+
13
+
14
+ class FailureLearner:
15
+ """Records agent failures and learns patterns to warn on similar future contexts.
16
+
17
+ Uses episodic memory to store failure records and FTS5 search to find
18
+ similar past failures when new errors occur. Warn-only: does not
19
+ prevent dispatch or automatic avoidance.
20
+ """
21
+
22
+ def __init__(self, db_path: str | None = None) -> None:
23
+ self.store: MemoryStore = MemoryStore(db_path=db_path) if db_path else MemoryStore()
24
+ self.episodic: EpisodicMemory = EpisodicMemory(self.store)
25
+
26
+ # ------------------------------------------------------------------
27
+ # Public API
28
+ # ------------------------------------------------------------------
29
+
30
+ def record_failure(
31
+ self,
32
+ context: str,
33
+ error_type: str,
34
+ error_message: str,
35
+ stack_trace: str | None = None,
36
+ ) -> int:
37
+ """Record a failure for future pattern matching.
38
+
39
+ Returns the memory ID of the stored failure record.
40
+ """
41
+ _require_memory_enabled()
42
+
43
+ sanitized_trace: str | None = (
44
+ self._sanitize_trace(stack_trace) if stack_trace else None
45
+ )
46
+
47
+ content = f"FAILURE: {error_type} | Context: {context} | Error: {error_message}"
48
+
49
+ metadata: dict[str, object] = {
50
+ "error_type": error_type,
51
+ "context": context,
52
+ "error_message": error_message,
53
+ "sanitized_trace": sanitized_trace,
54
+ "event_type": "failure",
55
+ }
56
+
57
+ return self.store.save(
58
+ content=content,
59
+ memory_type="episodic",
60
+ importance=0.9,
61
+ metadata=metadata,
62
+ )
63
+
64
+ def suggest_fix(
65
+ self,
66
+ error_type: str,
67
+ context: str,
68
+ limit: int = 3,
69
+ ) -> list[dict[str, object]]:
70
+ """Find past failures matching this error type and context.
71
+
72
+ Returns list of dicts with context, error_type, error_message,
73
+ memory_id, and relevance score.
74
+ """
75
+ _require_memory_enabled()
76
+
77
+ # FTS5 uses implicit AND; use OR for broader failure matching
78
+ raw_terms = f"{error_type} {context}".split()
79
+ query = " OR ".join(raw_terms)
80
+ results = self.episodic.recall(
81
+ query=query,
82
+ limit=limit,
83
+ event_type_filter="failure",
84
+ )
85
+
86
+ suggestions: list[dict[str, object]] = []
87
+ for result in results:
88
+ meta = _parse_metadata(result)
89
+ suggestions.append({
90
+ "context": str(meta.get("context", "")),
91
+ "error_type": str(meta.get("error_type", "")),
92
+ "error_message": str(meta.get("error_message", "")),
93
+ "memory_id": result.get("id", 0),
94
+ "relevance": float(cast(float, result.get("score", 0.0))),
95
+ })
96
+
97
+ return suggestions
98
+
99
+ def get_failure_patterns(
100
+ self,
101
+ min_occurrences: int = 2,
102
+ ) -> list[dict[str, object]]:
103
+ """Find recurring failure patterns grouped by error type.
104
+
105
+ Returns error types that have occurred at least *min_occurrences* times.
106
+ """
107
+ _require_memory_enabled()
108
+
109
+ conn = self.store.connect()
110
+ try:
111
+ rows = conn.execute(
112
+ "SELECT metadata FROM memories WHERE memory_type = 'episodic'"
113
+ ).fetchall()
114
+
115
+ counts: dict[str, int] = {}
116
+ for row in rows:
117
+ meta = _safe_parse_json(row["metadata"])
118
+ if meta.get("event_type") != "failure":
119
+ continue
120
+ error_type = str(meta.get("error_type", "unknown"))
121
+ counts[error_type] = counts.get(error_type, 0) + 1
122
+
123
+ return [
124
+ {"error_type": error_type, "count": count}
125
+ for error_type, count in sorted(counts.items())
126
+ if count >= min_occurrences
127
+ ]
128
+ finally:
129
+ conn.close()
130
+
131
+ # ------------------------------------------------------------------
132
+ # Internal helpers
133
+ # ------------------------------------------------------------------
134
+
135
+ @staticmethod
136
+ def _sanitize_trace(trace: str) -> str:
137
+ """Remove full file paths and line numbers from stack traces."""
138
+ sanitized = re.sub(
139
+ r'File "([^"]+)"',
140
+ lambda m: f'File "{os.path.basename(m.group(1))}"',
141
+ trace,
142
+ )
143
+ return re.sub(r", line \d+", "", sanitized)
144
+
145
+
146
+ # ------------------------------------------------------------------
147
+ # Module-level helpers
148
+ # ------------------------------------------------------------------
149
+
150
+
151
+ def _require_memory_enabled() -> None:
152
+ """Gate all public methods behind the experimental memory feature flag."""
153
+ memory_module = import_module("claude_experimental.memory")
154
+ require_enabled = getattr(memory_module, "_require_enabled", None)
155
+ if callable(require_enabled):
156
+ _ = require_enabled()
157
+ return
158
+ raise RuntimeError("claude_experimental.memory._require_enabled is unavailable")
159
+
160
+
161
+ def _parse_metadata(result: dict[str, object]) -> dict[str, object]:
162
+ """Parse metadata from a recall result dict."""
163
+ return _safe_parse_json(result.get("metadata"))
164
+
165
+
166
+ def _safe_parse_json(value: object) -> dict[str, object]:
167
+ """Safely parse a JSON string or coerce a dict; returns empty dict on failure."""
168
+ if isinstance(value, str):
169
+ try:
170
+ parsed = json.loads(value)
171
+ if isinstance(parsed, dict):
172
+ parsed_dict = cast(dict[object, object], parsed)
173
+ return {str(k): v for k, v in parsed_dict.items()}
174
+ except json.JSONDecodeError:
175
+ pass
176
+ return {}
177
+ if isinstance(value, dict):
178
+ raw_dict = cast(dict[object, object], value)
179
+ return {str(k): v for k, v in raw_dict.items()}
180
+ return {}
181
+
182
+
183
+ __all__ = ["FailureLearner"]
@@ -0,0 +1,169 @@
1
+ """Legacy Markdown Memory Migration Utility.
2
+
3
+ Migrates existing .omg/state/memory/*.md files to SQLite MemoryStore.
4
+ Preserves original files (copy, don't move) for safe rollback.
5
+ Idempotent: running twice doesn't create duplicates (dedup via content hash).
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import hashlib
10
+ import json
11
+ import os
12
+ import re
13
+ from datetime import datetime
14
+ from pathlib import Path
15
+ from typing import cast
16
+
17
+ from claude_experimental.memory.store import MemoryStore
18
+
19
+
20
+ def migrate_markdown_memories(
21
+ project_dir: str,
22
+ target_store: MemoryStore | None = None,
23
+ ) -> dict[str, int]:
24
+ """Migrate markdown memories from .omg/state/memory/ to SQLite store.
25
+
26
+ Args:
27
+ project_dir: Root project directory containing .omg/state/memory/
28
+ target_store: MemoryStore instance (default: project-scoped store)
29
+
30
+ Returns:
31
+ Dict with keys:
32
+ - files_found: Number of .md files discovered
33
+ - memories_migrated: Number successfully inserted
34
+ - errors: Number of files that failed to process
35
+ - skipped_duplicates: Number skipped due to content hash collision
36
+ """
37
+ store = target_store or MemoryStore(scope="project")
38
+ memory_dir = os.path.join(project_dir, ".omg", "state", "memory")
39
+
40
+ result = {
41
+ "files_found": 0,
42
+ "memories_migrated": 0,
43
+ "errors": 0,
44
+ "skipped_duplicates": 0,
45
+ }
46
+
47
+ # Handle missing directory gracefully
48
+ if not os.path.isdir(memory_dir):
49
+ return result
50
+
51
+ # Collect all .md files
52
+ md_files = sorted(
53
+ [f for f in os.listdir(memory_dir) if f.endswith(".md")],
54
+ reverse=True, # Process newest first
55
+ )
56
+ result["files_found"] = len(md_files)
57
+
58
+ for filename in md_files:
59
+ filepath = os.path.join(memory_dir, filename)
60
+
61
+ try:
62
+ # Read file content
63
+ with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
64
+ content = f.read()
65
+
66
+ if not content.strip():
67
+ result["errors"] += 1
68
+ continue
69
+
70
+ # Extract date from filename (format: YYYY-MM-DD-*.md)
71
+ date_match = re.match(r"(\d{4}-\d{2}-\d{2})", filename)
72
+ if date_match:
73
+ date_str = date_match.group(1)
74
+ try:
75
+ created_at = datetime.strptime(date_str, "%Y-%m-%d").timestamp()
76
+ except ValueError:
77
+ created_at = datetime.now().timestamp()
78
+ else:
79
+ created_at = datetime.now().timestamp()
80
+
81
+ # Compute content hash for deduplication
82
+ content_hash = hashlib.sha256(content.encode()).hexdigest()
83
+
84
+ # Check if memory with same content hash already exists
85
+ existing = _find_memory_by_content_hash(store, content_hash)
86
+ if existing is not None:
87
+ result["skipped_duplicates"] += 1
88
+ continue
89
+
90
+ # Calculate importance: normalized to 500 chars
91
+ importance = min(len(content) / 500.0, 1.0)
92
+
93
+ # Build metadata
94
+ metadata: dict[str, object] = {
95
+ "content_hash": content_hash,
96
+ "source_file": filename,
97
+ "migrated_from": "markdown",
98
+ }
99
+
100
+ # Insert into store
101
+ memory_id = store.save(
102
+ content=content,
103
+ memory_type="semantic",
104
+ importance=importance,
105
+ scope="project",
106
+ metadata=metadata,
107
+ )
108
+
109
+ if memory_id > 0:
110
+ result["memories_migrated"] += 1
111
+ else:
112
+ result["errors"] += 1
113
+
114
+ except (OSError, IOError) as e:
115
+ result["errors"] += 1
116
+ continue
117
+
118
+ return result
119
+
120
+
121
+ def _find_memory_by_content_hash(
122
+ store: MemoryStore, content_hash: str
123
+ ) -> dict[str, object] | None:
124
+ """Search for existing memory with matching content hash in metadata.
125
+
126
+ Args:
127
+ store: MemoryStore instance
128
+ content_hash: SHA256 hex digest to search for
129
+
130
+ Returns:
131
+ Memory dict if found, None otherwise
132
+ """
133
+ conn = store.connect()
134
+ try:
135
+ # Search metadata JSON for content_hash field
136
+ rows = cast(
137
+ list[tuple[object, ...]],
138
+ conn.execute(
139
+ """
140
+ SELECT id, content, memory_type, importance, scope, metadata,
141
+ created_at, accessed_at, access_count, schema_version
142
+ FROM memories
143
+ WHERE scope = ? AND json_extract(metadata, '$.content_hash') = ?
144
+ LIMIT 1
145
+ """,
146
+ ("project", content_hash),
147
+ ).fetchall(),
148
+ )
149
+
150
+ if rows:
151
+ row = rows[0]
152
+ return {
153
+ "id": row[0],
154
+ "content": row[1],
155
+ "memory_type": row[2],
156
+ "importance": row[3],
157
+ "scope": row[4],
158
+ "metadata": json.loads(row[5]) if isinstance(row[5], str) else row[5],
159
+ "created_at": row[6],
160
+ "accessed_at": row[7],
161
+ "access_count": row[8],
162
+ "schema_version": row[9],
163
+ }
164
+ return None
165
+ except Exception:
166
+ # If query fails (e.g., json_extract not available), return None
167
+ return None
168
+ finally:
169
+ conn.close()