synapse-hub 0.0.0-stage → 2.0.0

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 (122) hide show
  1. package/.agents/agents/failure-analyst.md +47 -0
  2. package/.agents/agents/live-explorer.md +41 -0
  3. package/.agents/agents/orchestrator.md +43 -0
  4. package/.agents/agents/pom-crafter.md +81 -0
  5. package/.agents/agents/quality-gatekeeper.md +47 -0
  6. package/.agents/agents/test-runner.md +43 -0
  7. package/.agents/rules/context-engineering.md +88 -0
  8. package/.agents/rules/git-push-protocol.md +101 -0
  9. package/.agents/rules/graphify.md +14 -0
  10. package/.agents/rules/lessons-learned.md +23 -0
  11. package/.agents/rules/mandatory-playwright-live.md +29 -0
  12. package/.agents/rules/skill-precedence.md +42 -0
  13. package/.agents/rules/systematic-debugging.md +99 -0
  14. package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +239 -0
  15. package/.agents/skills/antigravity-test-sentinel/SKILL.md +64 -0
  16. package/.agents/skills/api-network-mocking-interception/SKILL.md +142 -0
  17. package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +70 -0
  18. package/.agents/skills/architecture-decision-records/SKILL.md +441 -0
  19. package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +72 -0
  20. package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +214 -0
  21. package/.agents/skills/bats-testing-patterns/SKILL.md +100 -0
  22. package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -0
  23. package/.agents/skills/clean-pom-crafter/SKILL.md +75 -0
  24. package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -0
  25. package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -0
  26. package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +64 -0
  27. package/.agents/skills/code-refactoring-refactor-clean/resources/implementation-playbook.md +879 -0
  28. package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +123 -0
  29. package/.agents/skills/e2e-testing-patterns/SKILL.md +103 -0
  30. package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -0
  31. package/.agents/skills/failure-autopsy-and-healing/SKILL.md +54 -0
  32. package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -0
  33. package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -0
  34. package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -0
  35. package/.agents/skills/file-and-report-validator/SKILL.md +90 -0
  36. package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +114 -0
  37. package/.agents/skills/github-actions-templates/SKILL.md +334 -0
  38. package/.agents/skills/graphify-impact-analyzer/SKILL.md +78 -0
  39. package/.agents/skills/katalon-cli-runner/SKILL.md +76 -0
  40. package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -0
  41. package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -0
  42. package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -0
  43. package/.agents/skills/lam-script-generator/SKILL.md +83 -0
  44. package/.agents/skills/playwright-live-interaction/SKILL.md +86 -0
  45. package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -0
  46. package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -0
  47. package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -0
  48. package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -0
  49. package/.agents/skills/security-dast-sentinel/SKILL.md +176 -0
  50. package/.agents/skills/self-healing-locator/SKILL.md +73 -0
  51. package/.agents/skills/synthetic-test-data-generator/SKILL.md +135 -0
  52. package/.agents/skills/token-and-context-optimizer/SKILL.md +70 -0
  53. package/.agents/skills/visual-regression-sentinel/SKILL.md +62 -0
  54. package/.agents/skills/wcag-audit-patterns/SKILL.md +77 -0
  55. package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -0
  56. package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +65 -0
  57. package/.agents/templates/completion_report.md +57 -0
  58. package/.agents/workflows/graphify.md +10 -0
  59. package/.agents/workflows/test-impact.md +15 -0
  60. package/.agents/workflows/verify.md +34 -0
  61. package/AGENTS.md +115 -0
  62. package/README.md +394 -2
  63. package/bin/agent-qa.js +90 -0
  64. package/package.json +59 -4
  65. package/skills_manifest.json +262 -0
  66. package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +97 -0
  67. package/templates/PROJECT_STATE_TEMPLATE.md +20 -0
  68. package/tools/TOOLS_RUNBOOK.md +298 -0
  69. package/tools/agent_canary.py +501 -0
  70. package/tools/analyze_project.py +242 -0
  71. package/tools/api_drift_sentinel.py +208 -0
  72. package/tools/auto_heal.py +189 -0
  73. package/tools/browser_helpers.js +593 -0
  74. package/tools/bug_reporter.py +224 -0
  75. package/tools/cache_resolver.py +846 -0
  76. package/tools/check_env.py +288 -0
  77. package/tools/ci_healing_hook.py +128 -0
  78. package/tools/cli.py +402 -0
  79. package/tools/config_decoupler.py +253 -0
  80. package/tools/db_sandbox.py +341 -0
  81. package/tools/distill_log.py +152 -0
  82. package/tools/e2e_flow_prober.ps1 +65 -0
  83. package/tools/flaky_detector.py +222 -0
  84. package/tools/fuzz_payloads.py +197 -0
  85. package/tools/generate_allure_report.ps1 +30 -0
  86. package/tools/generate_api_matrix.py +193 -0
  87. package/tools/generate_tc_from_story.py +182 -0
  88. package/tools/generate_test_reports.ps1 +135 -0
  89. package/tools/git_pre_commit_guard.py +203 -0
  90. package/tools/heal_pr_automator.py +195 -0
  91. package/tools/heuristic_locator_matcher.py +202 -0
  92. package/tools/infer_linter.py +238 -0
  93. package/tools/lint_ai_docs.py +254 -0
  94. package/tools/lookup_tc.py +165 -0
  95. package/tools/lookup_xpath.py +167 -0
  96. package/tools/matrix_sharder.py +146 -0
  97. package/tools/mcp_excel_server.ps1 +251 -0
  98. package/tools/mcp_server.py +490 -0
  99. package/tools/micro_mutator.py +309 -0
  100. package/tools/network_mock_shield.py +170 -0
  101. package/tools/omni_emitter.py +500 -0
  102. package/tools/omni_parser.py +435 -0
  103. package/tools/omni_transpiler.py +168 -0
  104. package/tools/pinpoint_failure.py +88 -0
  105. package/tools/project_state.py +169 -0
  106. package/tools/repo_map.json +12 -0
  107. package/tools/repo_map.py +277 -0
  108. package/tools/repo_map.txt +7 -0
  109. package/tools/safety_firewall.py +245 -0
  110. package/tools/setup_project.py +265 -0
  111. package/tools/smart_visual_masker.py +134 -0
  112. package/tools/test_impact.py +321 -0
  113. package/tools/tms_sync_bridge.py +342 -0
  114. package/tools/trace_data_flow.py +240 -0
  115. package/tools/traffic_session_converter.py +256 -0
  116. package/tools/universal_ir.py +480 -0
  117. package/tools/validate_groovy_scripts.py +173 -0
  118. package/tools/verify_all.py +496 -0
  119. package/tools/verify_all.sh +4 -0
  120. package/tools/visual_diff_canvas.py +142 -0
  121. package/tools/visual_som_helper.py +418 -0
  122. package/tools/wcag_audit.py +214 -0
@@ -0,0 +1,846 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Universal Local Semantic Memory Cache & Self-Healing Resolver
4
+ -------------------------------------------------------------
5
+ Provides 100% Offline, Zero-Token diagnostic resolution for known test failures.
6
+ Saves solutions directly into an SQLite database (tools/memory_cache.db).
7
+ When identical failures occur, returns pre-tested patches with 0 LLM Tokens.
8
+
9
+ Features:
10
+ 1. Positive Memory Cache (healing_cache): Pre-tested, verified code patches.
11
+ 2. Negative Memory Cache (failed_fixes): Blacklist tracking incorrect patches,
12
+ preventing AI Agents from falling into Blind Retry Loops.
13
+ """
14
+
15
+ import sys
16
+ import os
17
+ import sqlite3
18
+ import json
19
+ import argparse
20
+ import re
21
+ import math
22
+ import time
23
+ import functools
24
+ from collections import Counter
25
+ from datetime import datetime
26
+
27
+ # Enforce UTF-8 stdout encoding for Windows console compatibility
28
+ if hasattr(sys.stdout, 'reconfigure'):
29
+ try:
30
+ sys.stdout.reconfigure(encoding='utf-8')
31
+ sys.stderr.reconfigure(encoding='utf-8')
32
+ except Exception:
33
+ pass
34
+
35
+ GLOBAL_DIR = os.path.expanduser("~/.agent_memory")
36
+ GLOBAL_DB = os.path.join(GLOBAL_DIR, "global_cache.db")
37
+ LOCAL_DB = os.path.join(os.path.dirname(os.path.abspath(__file__)), "memory_cache.db")
38
+
39
+
40
+ def resolve_db_path(use_local=False):
41
+ """Returns the SQLite database path. Defaults to Global Central Memory."""
42
+ if use_local:
43
+ return LOCAL_DB
44
+ os.makedirs(GLOBAL_DIR, exist_ok=True)
45
+ return GLOBAL_DB
46
+
47
+
48
+ DB_FILE = resolve_db_path()
49
+
50
+ SEED_SOLUTIONS = [
51
+ {
52
+ "error_signature": "ElementClickInterceptedException",
53
+ "module_name": "GLOBAL",
54
+ "symptom": "Modal backdrop or PrimeNG overlay (.p-component-overlay) blocks button click.",
55
+ "root_cause": "Angular PrimeNG animation fade-out latency.",
56
+ "groovy_patch": "((JavascriptExecutor) driver).executeScript(\"arguments[0].click();\", element);"
57
+ },
58
+ {
59
+ "error_signature": "Save_Button_Disabled",
60
+ "module_name": "GLOBAL",
61
+ "symptom": "Save button [ Submit ] remains dimmed/disabled.",
62
+ "root_cause": "Angular Reactive Form missing synthetic input/change event or required field empty.",
63
+ "groovy_patch": "WebElement el = findElement(By.cssSelector(\"input[formcontrolname='code']\"));\njsSetInput(el, uniqueCode);"
64
+ },
65
+ {
66
+ "error_signature": "StaleElementReferenceException",
67
+ "module_name": "GLOBAL",
68
+ "symptom": "Element is not attached to the DOM.",
69
+ "root_cause": "Angular re-rendered DOM node after async API response.",
70
+ "groovy_patch": "for (int i = 0; i < 3; i++) {\n try {\n findElement(by).click();\n break;\n } catch (StaleElementReferenceException e) {\n sleep(500);\n }\n}"
71
+ },
72
+ {
73
+ "error_signature": "Duplicate_Code_Error",
74
+ "module_name": "GLOBAL",
75
+ "symptom": "Toast alert: 'Code already exists' or 'Data already exists'.",
76
+ "root_cause": "Hardcoded or non-unique test identifier collided with existing record.",
77
+ "groovy_patch": "String code = BasePage.uniqueCode(\"AUTO_\");"
78
+ },
79
+ {
80
+ "error_signature": "PrimeNG_Dropdown_Empty",
81
+ "module_name": "GLOBAL",
82
+ "symptom": "Dropdown item not clicked or panel not visible.",
83
+ "root_cause": "Overlay panel (.p-dropdown-panel) has not rendered when click dispatched.",
84
+ "groovy_patch": "waitForElementVisible(By.cssSelector(\".p-dropdown-panel\"), 5);\nfindElement(By.xpath(\"//li[contains(., '\" + text + \"')]\")).click();"
85
+ },
86
+ {
87
+ "error_signature": "BlockUI_TimeoutException",
88
+ "module_name": "GLOBAL",
89
+ "symptom": "Timeout waiting for table or form interaction.",
90
+ "root_cause": "PrimeNG loading mask (.p-blockui, .p-progress-spinner) still active.",
91
+ "groovy_patch": "waitForElementNotVisible(By.cssSelector(\".p-blockui, .p-progress-spinner\"), 10);"
92
+ }
93
+ ]
94
+
95
+ SEED_LESSONS_LEARNED = [
96
+ {
97
+ "page_context": "Checkout / Payment",
98
+ "error_signature": "ElementClickInterceptedException",
99
+ "root_cause": "Debounce 300ms hoặc PrimeNG overlay (.p-component-overlay) đang mờ dần.",
100
+ "failed_attempt": "findElement(By.id('confirm-btn')).click();",
101
+ "successful_fix": "waitForElementNotVisible('.p-component-overlay', 5); jsClick(btn);",
102
+ "lesson_summary": "Tại Checkout/Payment, button Confirm bị debounce hoặc overlay animation; không click trực tiếp mà phải đợi overlay biến mất hoặc dùng clickJS/clickSafe."
103
+ },
104
+ {
105
+ "page_context": "Auth / Reactive Forms",
106
+ "error_signature": "Save_Button_Disabled / FormInvalid",
107
+ "root_cause": "Angular Reactive Form không nhận native DOM value do thiếu Zone.js synthetic input event.",
108
+ "failed_attempt": "input.sendKeys(uniqueCode); submitBtn.click();",
109
+ "successful_fix": "jsSetInput(input, uniqueCode); qa.audit();",
110
+ "lesson_summary": "Tại các form Angular/React, sendKeys chay không trigger validity; bắt buộc dispatch synthetic input/change event hoặc dùng qa.audit."
111
+ },
112
+ {
113
+ "page_context": "Data Mining / Async Table",
114
+ "error_signature": "StaleElementReferenceException",
115
+ "root_cause": "Bảng dữ liệu re-render DOM node sau khi nhận API response bất đồng bộ.",
116
+ "failed_attempt": "tableRows.get(0).click();",
117
+ "successful_fix": "waitForElementNotVisible('.p-blockui, .p-progress-spinner', 10); retryClick(row);",
118
+ "lesson_summary": "Tại bảng dữ liệu async, tuyệt đối không click row ngay lập tức; phải đợi BlockUI/Spinner biến mất hoàn toàn để tránh Stale Element."
119
+ }
120
+ ]
121
+
122
+ SEED_FAILED_FIXES = [
123
+ {
124
+ "error_signature": "StaleElementReferenceException",
125
+ "attempted_patch": "Thread.sleep(3000); findElement(by).click();",
126
+ "failure_reason": "Causes pipeline flakiness; fails whenever network lag or server response exceeds static sleep duration.",
127
+ "agent_role": "pom-crafter"
128
+ },
129
+ {
130
+ "error_signature": "ElementClickInterceptedException",
131
+ "attempted_patch": "new Actions(driver).moveToElement(el).click().perform();",
132
+ "failure_reason": "Still blocked by modal backdrop because backdrop element sits physically above target element in z-index.",
133
+ "agent_role": "failure-analyst"
134
+ },
135
+ {
136
+ "error_signature": "Save_Button_Disabled",
137
+ "attempted_patch": "el.sendKeys(uniqueCode);",
138
+ "failure_reason": "Standard Selenium sendKeys does not fire Angular Zone.js synthetic input/change events, leaving formControl invalid.",
139
+ "agent_role": "pom-crafter"
140
+ }
141
+ ]
142
+
143
+
144
+ def retry_on_locked(max_retries=5, initial_delay=0.05, backoff=2.0):
145
+ """Decorator to retry SQLite operations if database is locked or busy."""
146
+ def decorator(func):
147
+ @functools.wraps(func)
148
+ def wrapper(*args, **kwargs):
149
+ delay = initial_delay
150
+ last_err = None
151
+ for attempt in range(max_retries):
152
+ try:
153
+ return func(*args, **kwargs)
154
+ except sqlite3.OperationalError as e:
155
+ last_err = e
156
+ err_msg = str(e).lower()
157
+ if "locked" in err_msg or "busy" in err_msg:
158
+ time.sleep(delay)
159
+ delay *= backoff
160
+ else:
161
+ raise
162
+ if last_err:
163
+ raise last_err
164
+ return wrapper
165
+ return decorator
166
+
167
+
168
+ def get_db(db_path=None, timeout=30.0):
169
+ """Returns a resilient SQLite connection configured with WAL mode and busy timeout."""
170
+ target = db_path or DB_FILE
171
+ conn = sqlite3.connect(target, timeout=timeout)
172
+ conn.row_factory = sqlite3.Row
173
+ try:
174
+ conn.execute("PRAGMA journal_mode=WAL;")
175
+ conn.execute("PRAGMA synchronous=NORMAL;")
176
+ conn.execute("PRAGMA busy_timeout=30000;")
177
+ except Exception:
178
+ pass
179
+ return conn
180
+
181
+
182
+ @retry_on_locked()
183
+ def init_db(seed=True, target_db=None):
184
+ current_db = target_db or DB_FILE
185
+ with get_db(current_db) as conn:
186
+ cursor = conn.cursor()
187
+ # 1. Positive Memory Table (Verified Working Patches)
188
+ cursor.execute("""
189
+ CREATE TABLE IF NOT EXISTS healing_cache (
190
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
191
+ error_signature TEXT NOT NULL,
192
+ module_name TEXT DEFAULT 'GLOBAL',
193
+ symptom TEXT,
194
+ root_cause TEXT,
195
+ groovy_patch TEXT NOT NULL,
196
+ hit_count INTEGER DEFAULT 0,
197
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
198
+ UNIQUE(error_signature, module_name)
199
+ )
200
+ """)
201
+
202
+ # 2. Negative Memory Table (Blacklist of Failed Fix Attempts)
203
+ cursor.execute("""
204
+ CREATE TABLE IF NOT EXISTS failed_fixes (
205
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
206
+ error_signature TEXT NOT NULL,
207
+ attempted_patch TEXT NOT NULL,
208
+ failure_reason TEXT NOT NULL,
209
+ agent_role TEXT DEFAULT 'pom-crafter',
210
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
211
+ )
212
+ """)
213
+
214
+ # 3. Episodic Reflexion Table (Historical Lessons Learned)
215
+ cursor.execute("""
216
+ CREATE TABLE IF NOT EXISTS lessons_learned (
217
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
218
+ page_context TEXT DEFAULT 'GLOBAL',
219
+ error_signature TEXT NOT NULL,
220
+ root_cause TEXT,
221
+ failed_attempt TEXT,
222
+ successful_fix TEXT,
223
+ lesson_summary TEXT NOT NULL,
224
+ hit_count INTEGER DEFAULT 0,
225
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
226
+ )
227
+ """)
228
+ conn.commit()
229
+
230
+ # Migrate existing solutions from local DB if initializing global DB for the first time
231
+ if current_db == GLOBAL_DB and os.path.exists(LOCAL_DB):
232
+ try:
233
+ local_conn = get_db(LOCAL_DB)
234
+ rows = local_conn.execute("SELECT * FROM healing_cache").fetchall()
235
+ for r in rows:
236
+ cursor.execute("""
237
+ INSERT OR IGNORE INTO healing_cache
238
+ (error_signature, module_name, symptom, root_cause, groovy_patch, hit_count)
239
+ VALUES (?, ?, ?, ?, ?, ?)
240
+ """, (r["error_signature"], r["module_name"], r["symptom"], r["root_cause"], r["groovy_patch"], r["hit_count"]))
241
+ conn.commit()
242
+ local_conn.close()
243
+ except Exception:
244
+ pass
245
+
246
+ if seed:
247
+ for s in SEED_SOLUTIONS:
248
+ cursor.execute("""
249
+ INSERT OR IGNORE INTO healing_cache
250
+ (error_signature, module_name, symptom, root_cause, groovy_patch)
251
+ VALUES (?, ?, ?, ?, ?)
252
+ """, (s["error_signature"], s["module_name"], s["symptom"], s["root_cause"], s["groovy_patch"]))
253
+
254
+ for f in SEED_FAILED_FIXES:
255
+ cursor.execute("""
256
+ SELECT COUNT(*) as cnt FROM failed_fixes
257
+ WHERE error_signature = ? AND attempted_patch = ?
258
+ """, (f["error_signature"], f["attempted_patch"]))
259
+ if cursor.fetchone()["cnt"] == 0:
260
+ cursor.execute("""
261
+ INSERT INTO failed_fixes (error_signature, attempted_patch, failure_reason, agent_role)
262
+ VALUES (?, ?, ?, ?)
263
+ """, (f["error_signature"], f["attempted_patch"], f["failure_reason"], f["agent_role"]))
264
+
265
+ for l in SEED_LESSONS_LEARNED:
266
+ cursor.execute("""
267
+ SELECT COUNT(*) as cnt FROM lessons_learned
268
+ WHERE error_signature = ? AND page_context = ?
269
+ """, (l["error_signature"], l["page_context"]))
270
+ if cursor.fetchone()["cnt"] == 0:
271
+ cursor.execute("""
272
+ INSERT INTO lessons_learned
273
+ (page_context, error_signature, root_cause, failed_attempt, successful_fix, lesson_summary)
274
+ VALUES (?, ?, ?, ?, ?, ?)
275
+ """, (l["page_context"], l["error_signature"], l["root_cause"], l["failed_attempt"], l["successful_fix"], l["lesson_summary"]))
276
+ conn.commit()
277
+
278
+
279
+ # Stop-words list filtering generic stacktrace, code runtime & English grammar noise
280
+ STOP_WORDS = {
281
+ # English stop words
282
+ "a", "about", "above", "after", "again", "against", "all", "am", "an", "and",
283
+ "any", "are", "aren't", "as", "at", "be", "because", "been", "before", "being",
284
+ "below", "between", "both", "but", "by", "can", "cannot", "could", "couldn't",
285
+ "did", "didn't", "do", "does", "doesn't", "doing", "don't", "down", "during",
286
+ "each", "few", "for", "from", "further", "had", "hadn't", "has", "hasn't",
287
+ "have", "haven't", "having", "he", "her", "here", "hers", "herself", "him",
288
+ "himself", "his", "how", "i", "if", "in", "into", "is", "isn't", "it", "it's",
289
+ "its", "itself", "let's", "me", "more", "most", "mustn't", "my", "myself",
290
+ "no", "nor", "not", "of", "off", "on", "once", "only", "or", "other", "ought",
291
+ "our", "ours", "ourselves", "out", "over", "own", "same", "shan't", "she",
292
+ "should", "shouldn't", "so", "some", "such", "than", "that", "that's", "the",
293
+ "their", "theirs", "them", "themselves", "then", "there", "there's", "these",
294
+ "they", "they'd", "they'll", "they're", "they've", "this", "those", "through",
295
+ "to", "too", "under", "until", "up", "very", "was", "wasn't", "we", "we'd",
296
+ "we'll", "we're", "we've", "were", "weren't", "what", "what's", "when", "where",
297
+ "which", "while", "who", "whom", "why", "with", "won't", "would", "wouldn't",
298
+ "you", "you'd", "you'll", "you're", "you've", "your", "yours", "yourself",
299
+
300
+ # Java / Groovy / Selenium / Katalon Stacktrace & Runtime Boilerplate Noise
301
+ "org", "com", "net", "io", "java", "lang", "util", "reflect", "sun", "native",
302
+ "impl", "accessor", "unknown", "source", "line", "method", "thread", "run",
303
+ "call", "invoke", "invoker", "kms", "katalon", "core", "webui", "keyword",
304
+ "builtin", "driver", "webdriver", "remote", "http", "internal", "client",
305
+ "server", "session", "step", "tc", "test", "tests", "case", "suite", "runner",
306
+ "element", "elements", "locator", "find", "finding", "found", "selenium",
307
+ "openqa", "chrome", "firefox", "edge", "safari", "webkit",
308
+
309
+ # Generic Error Noise (present in almost all failure traces)
310
+ "exception", "error", "failed", "failure", "caused", "cause", "trace",
311
+ "stacktrace", "message", "warning", "info", "debug", "null", "object"
312
+ }
313
+
314
+
315
+ def tokenize(text):
316
+ """
317
+ Extracts tokens with CamelCase decomposition and Stop-words stripping.
318
+ Preserves specific domain identifiers while eliminating generic stacktrace noise.
319
+ """
320
+ if not text:
321
+ return []
322
+ words = re.findall(r'[a-zA-Z0-9_\-]+', text)
323
+ tokens = []
324
+ for w in words:
325
+ w_lower = w.lower().strip("-_")
326
+ # Filter Katalon / Framework built-in keyword class noise (e.g. ClickKeyword, SetTextKeyword)
327
+ if w_lower.endswith("keyword"):
328
+ continue
329
+ if len(w_lower) >= 2 and w_lower not in STOP_WORDS:
330
+ tokens.append(w_lower)
331
+ # Split CamelCase: "ElementClickIntercepted" -> "click", "intercepted"
332
+ camel_parts = re.findall(r'[A-Z]?[a-z]+|[A-Z]+(?=[A-Z][a-z]|\d|\W|$)|\d+', w)
333
+ if len(camel_parts) > 1:
334
+ for cp in camel_parts:
335
+ cp_lower = cp.lower().strip("-_")
336
+ if len(cp_lower) >= 2 and cp_lower not in STOP_WORDS and not cp_lower.endswith("keyword"):
337
+ tokens.append(cp_lower)
338
+ return tokens
339
+
340
+
341
+ def compute_tfidf_similarities(query, documents):
342
+ """
343
+ Computes TF-IDF weighted cosine similarity between a query and a candidate document corpus.
344
+ Applies inverse document frequency to dampen frequent boilerplate words and amplify
345
+ rare diagnostic markers (e.g. 'stale', 'intercepted', 'ng-invalid').
346
+ """
347
+ query_tokens = tokenize(query)
348
+ if not query_tokens or not documents:
349
+ return [0.0] * len(documents)
350
+
351
+ doc_tokens_list = [tokenize(doc) for doc in documents]
352
+ all_docs = [query_tokens] + doc_tokens_list
353
+ total_docs = len(all_docs)
354
+
355
+ # Document Frequency across the combined corpus
356
+ df = Counter()
357
+ for doc in all_docs:
358
+ for token in set(doc):
359
+ df[token] += 1
360
+
361
+ # Smoothed IDF: ln(1 + N / (1 + df)) + 1
362
+ idf = {t: math.log(1.0 + total_docs / (1.0 + freq)) + 1.0 for t, freq in df.items()}
363
+
364
+ def get_tfidf_vec(tokens):
365
+ tf = Counter(tokens)
366
+ vec = {}
367
+ for t, count in tf.items():
368
+ vec[t] = (1.0 + math.log(count)) * idf.get(t, 1.0)
369
+ return vec
370
+
371
+ def norm(vec):
372
+ return math.sqrt(sum(val ** 2 for val in vec.values()))
373
+
374
+ query_vec = get_tfidf_vec(query_tokens)
375
+ query_norm = norm(query_vec)
376
+ if not query_norm:
377
+ return [0.0] * len(documents)
378
+
379
+ scores = []
380
+ for doc_tokens in doc_tokens_list:
381
+ if not doc_tokens:
382
+ scores.append(0.0)
383
+ continue
384
+ doc_vec = get_tfidf_vec(doc_tokens)
385
+ doc_norm = norm(doc_vec)
386
+ if not doc_norm:
387
+ scores.append(0.0)
388
+ continue
389
+ dot = sum(query_vec[t] * doc_vec[t] for t in set(query_vec.keys()) & set(doc_vec.keys()))
390
+ scores.append(float(dot) / (query_norm * doc_norm))
391
+
392
+ return scores
393
+
394
+
395
+ def compute_cosine_similarity(text1, text2):
396
+ """
397
+ Computes tokenized Cosine Similarity between two texts with stop-words filtration.
398
+ """
399
+ tokens1 = tokenize(text1)
400
+ tokens2 = tokenize(text2)
401
+ if not tokens1 or not tokens2:
402
+ return 0.0
403
+ vec1 = Counter(tokens1)
404
+ vec2 = Counter(tokens2)
405
+ intersection = set(vec1.keys()) & set(vec2.keys())
406
+ if not intersection:
407
+ return 0.0
408
+ numerator = sum([vec1[x] * vec2[x] for x in intersection])
409
+ sum1 = sum([val**2 for val in vec1.values()])
410
+ sum2 = sum([val**2 for val in vec2.values()])
411
+ denominator = math.sqrt(sum1) * math.sqrt(sum2)
412
+ if not denominator:
413
+ return 0.0
414
+ return float(numerator) / denominator
415
+
416
+
417
+ def get_failed_fixes(error_signature):
418
+ """Retrieves all previously recorded failed fix attempts for this error signature."""
419
+ init_db(seed=True)
420
+ with get_db() as conn:
421
+ cursor = conn.cursor()
422
+ cursor.execute("""
423
+ SELECT * FROM failed_fixes
424
+ WHERE error_signature = ? OR error_signature LIKE ?
425
+ ORDER BY created_at DESC
426
+ """, (error_signature, f"%{error_signature}%"))
427
+ return [dict(r) for r in cursor.fetchall()]
428
+
429
+
430
+ @retry_on_locked()
431
+ def record_failed_fix(error_signature, attempted_patch, failure_reason, agent_role="pom-crafter"):
432
+ """Records a failed bug fix attempt into negative memory so AI agents will never repeat it."""
433
+ init_db(seed=True)
434
+ with get_db() as conn:
435
+ cursor = conn.cursor()
436
+ cursor.execute("""
437
+ INSERT INTO failed_fixes (error_signature, attempted_patch, failure_reason, agent_role)
438
+ VALUES (?, ?, ?, ?)
439
+ """, (error_signature, attempted_patch, failure_reason, agent_role))
440
+ conn.commit()
441
+ return True
442
+
443
+
444
+ def query_solution(error_signature, module_name="GLOBAL", threshold=0.35):
445
+ """
446
+ Two-Way Query:
447
+ 1. Returns verified working patch from positive memory.
448
+ 2. Attaches blacklist of previously failed fixes from negative memory.
449
+ """
450
+ init_db(seed=True)
451
+ with get_db() as conn:
452
+ cursor = conn.cursor()
453
+ # 1. Exact match with module
454
+ cursor.execute("""
455
+ SELECT * FROM healing_cache
456
+ WHERE error_signature = ? AND module_name = ?
457
+ """, (error_signature, module_name))
458
+ row = cursor.fetchone()
459
+ match_type = "EXACT_MODULE"
460
+
461
+ # 2. Fallback to GLOBAL match
462
+ if not row:
463
+ cursor.execute("""
464
+ SELECT * FROM healing_cache
465
+ WHERE error_signature = ? AND module_name = 'GLOBAL'
466
+ """, (error_signature,))
467
+ row = cursor.fetchone()
468
+ match_type = "GLOBAL_MATCH"
469
+
470
+ # 3. Fuzzy search on symptom/error
471
+ if not row:
472
+ cursor.execute("""
473
+ SELECT * FROM healing_cache
474
+ WHERE error_signature LIKE ? OR symptom LIKE ?
475
+ """, (f"%{error_signature}%", f"%{error_signature}%"))
476
+ row = cursor.fetchone()
477
+ match_type = "FUZZY_SQL"
478
+
479
+ # 4. Offline Semantic Vector TF-IDF Cosine Similarity
480
+ confidence = 1.0
481
+ if not row:
482
+ cursor.execute("SELECT * FROM healing_cache")
483
+ all_rows = cursor.fetchall()
484
+ if all_rows:
485
+ candidates_corpus = [
486
+ f"{c['error_signature']} {c['symptom']} {c['root_cause']}"
487
+ for c in all_rows
488
+ ]
489
+ scores = compute_tfidf_similarities(error_signature, candidates_corpus)
490
+ best_idx = -1
491
+ best_score = 0.0
492
+ for idx, score in enumerate(scores):
493
+ if score > best_score:
494
+ best_score = score
495
+ best_idx = idx
496
+
497
+ if best_idx >= 0 and best_score >= threshold:
498
+ row = all_rows[best_idx]
499
+ match_type = f"SEMANTIC_TFIDF ({best_score*100:.1f}%)"
500
+ confidence = best_score
501
+
502
+ # Query negative memory for this error
503
+ failed_fixes = get_failed_fixes(error_signature)
504
+
505
+ if row:
506
+ cursor.execute("""
507
+ UPDATE healing_cache
508
+ SET hit_count = hit_count + 1
509
+ WHERE id = ?
510
+ """, (row["id"],))
511
+ conn.commit()
512
+ result = dict(row)
513
+ result["match_type"] = match_type
514
+ result["confidence"] = confidence
515
+ result["failed_fixes"] = failed_fixes
516
+ return result
517
+ elif failed_fixes:
518
+ # No solution yet, but we have failed fixes recorded!
519
+ return {
520
+ "error_signature": error_signature,
521
+ "module_name": module_name,
522
+ "symptom": "Unknown solution",
523
+ "root_cause": "Unknown",
524
+ "groovy_patch": None,
525
+ "match_type": "NEGATIVE_MEMORY_ONLY",
526
+ "confidence": 0.0,
527
+ "failed_fixes": failed_fixes
528
+ }
529
+ return None
530
+
531
+
532
+ @retry_on_locked()
533
+ def learn_solution(error_signature, module_name, symptom, root_cause, groovy_patch):
534
+ """Saves a verified solution into positive memory."""
535
+ init_db(seed=True)
536
+ with get_db() as conn:
537
+ cursor = conn.cursor()
538
+ cursor.execute("""
539
+ INSERT INTO healing_cache (error_signature, module_name, symptom, root_cause, groovy_patch)
540
+ VALUES (?, ?, ?, ?, ?)
541
+ ON CONFLICT(error_signature, module_name) DO UPDATE SET
542
+ symptom = excluded.symptom,
543
+ root_cause = excluded.root_cause,
544
+ groovy_patch = excluded.groovy_patch
545
+ """, (error_signature, module_name or "GLOBAL", symptom, root_cause, groovy_patch))
546
+ conn.commit()
547
+ return True
548
+
549
+
550
+ @retry_on_locked()
551
+ def sync_db():
552
+ """Bidirectional synchronization between Global Central Memory and Local Project Cache."""
553
+ global DB_FILE
554
+ # Initialize schemas for both Global and Local stores prior to sync
555
+ DB_FILE = resolve_db_path(use_local=False)
556
+ init_db(seed=True, target_db=GLOBAL_DB)
557
+ DB_FILE = resolve_db_path(use_local=True)
558
+ init_db(seed=False, target_db=LOCAL_DB)
559
+
560
+ g_conn = get_db(GLOBAL_DB)
561
+ l_conn = get_db(LOCAL_DB)
562
+
563
+ # 1. Sync positive cache
564
+ g_rows = g_conn.execute("SELECT * FROM healing_cache").fetchall()
565
+ for r in g_rows:
566
+ l_conn.execute("""
567
+ INSERT INTO healing_cache (error_signature, module_name, symptom, root_cause, groovy_patch, hit_count)
568
+ VALUES (?, ?, ?, ?, ?, ?)
569
+ ON CONFLICT(error_signature, module_name) DO UPDATE SET
570
+ symptom = excluded.symptom,
571
+ root_cause = excluded.root_cause,
572
+ groovy_patch = excluded.groovy_patch
573
+ """, (r["error_signature"], r["module_name"], r["symptom"], r["root_cause"], r["groovy_patch"], r["hit_count"]))
574
+ l_conn.commit()
575
+
576
+ l_rows = l_conn.execute("SELECT * FROM healing_cache").fetchall()
577
+ for r in l_rows:
578
+ g_conn.execute("""
579
+ INSERT INTO healing_cache (error_signature, module_name, symptom, root_cause, groovy_patch, hit_count)
580
+ VALUES (?, ?, ?, ?, ?, ?)
581
+ ON CONFLICT(error_signature, module_name) DO UPDATE SET
582
+ symptom = excluded.symptom,
583
+ root_cause = excluded.root_cause,
584
+ groovy_patch = excluded.groovy_patch
585
+ """, (r["error_signature"], r["module_name"], r["symptom"], r["root_cause"], r["groovy_patch"], r["hit_count"]))
586
+ g_conn.commit()
587
+
588
+ # 2. Sync negative failed fixes
589
+ g_failed = g_conn.execute("SELECT * FROM failed_fixes").fetchall()
590
+ for f in g_failed:
591
+ l_conn.execute("""
592
+ INSERT OR IGNORE INTO failed_fixes (error_signature, attempted_patch, failure_reason, agent_role)
593
+ VALUES (?, ?, ?, ?)
594
+ """, (f["error_signature"], f["attempted_patch"], f["failure_reason"], f["agent_role"]))
595
+ l_conn.commit()
596
+
597
+ # 3. Sync lessons learned
598
+ g_lessons = g_conn.execute("SELECT * FROM lessons_learned").fetchall()
599
+ for r in g_lessons:
600
+ l_conn.execute("""
601
+ INSERT OR IGNORE INTO lessons_learned
602
+ (page_context, error_signature, root_cause, failed_attempt, successful_fix, lesson_summary, hit_count)
603
+ VALUES (?, ?, ?, ?, ?, ?, ?)
604
+ """, (r["page_context"], r["error_signature"], r["root_cause"], r["failed_attempt"], r["successful_fix"], r["lesson_summary"], r["hit_count"]))
605
+ l_conn.commit()
606
+
607
+ l_conn.close()
608
+ g_conn.close()
609
+ return len(g_rows)
610
+
611
+
612
+ @retry_on_locked()
613
+ def record_lesson(page_context, error_signature, root_cause, failed_attempt, successful_fix, lesson_summary):
614
+ """
615
+ Episodic Reflexion Memory:
616
+ Records a concise lesson learned (maximum 2 lines) into SQLite for cross-session reflection.
617
+ """
618
+ init_db(seed=True)
619
+ with get_db() as conn:
620
+ cursor = conn.cursor()
621
+ cursor.execute("""
622
+ INSERT INTO lessons_learned
623
+ (page_context, error_signature, root_cause, failed_attempt, successful_fix, lesson_summary)
624
+ VALUES (?, ?, ?, ?, ?, ?)
625
+ """, (page_context or "GLOBAL", error_signature, root_cause, failed_attempt, successful_fix, lesson_summary.strip()))
626
+ conn.commit()
627
+ return True
628
+
629
+
630
+ def query_lessons(context_or_keyword, limit=2):
631
+ """
632
+ Episodic Reflexion Memory Query:
633
+ Retrieves the top N (default: 2) most relevant historical lessons learned for a given page, flow, or error.
634
+ Returns: List[Dict[str, Any]]
635
+ """
636
+ init_db(seed=True)
637
+ if not context_or_keyword:
638
+ context_or_keyword = "GLOBAL"
639
+
640
+ with get_db() as conn:
641
+ cursor = conn.cursor()
642
+ rows = cursor.execute("SELECT * FROM lessons_learned").fetchall()
643
+ if not rows:
644
+ return []
645
+
646
+ scored = []
647
+ q_lower = context_or_keyword.lower()
648
+ for r in rows:
649
+ combined = f"{r['page_context']} {r['error_signature']} {r['lesson_summary']} {r['root_cause'] or ''}"
650
+ sim_score = compute_cosine_similarity(context_or_keyword, combined)
651
+
652
+ # Boost score for explicit matches in page_context or error_signature
653
+ if r["page_context"] and r["page_context"].lower() in q_lower:
654
+ sim_score += 0.40
655
+ if r["error_signature"] and r["error_signature"].lower() in q_lower:
656
+ sim_score += 0.35
657
+
658
+ scored.append((sim_score, dict(r)))
659
+
660
+ scored.sort(key=lambda x: x[0], reverse=True)
661
+ top_results = [item[1] for item in scored[:limit]]
662
+
663
+ for item in top_results:
664
+ cursor.execute("UPDATE lessons_learned SET hit_count = hit_count + 1 WHERE id = ?", (item["id"],))
665
+ conn.commit()
666
+
667
+ return top_results
668
+
669
+
670
+ def get_stats():
671
+ init_db(seed=True)
672
+ with get_db() as conn:
673
+ cursor = conn.cursor()
674
+ cursor.execute("SELECT COUNT(*) as total_solutions, SUM(hit_count) as total_hits FROM healing_cache")
675
+ row = cursor.fetchone()
676
+ total_sol = row["total_solutions"] or 0
677
+ total_hits = row["total_hits"] or 0
678
+ saved_tokens = total_hits * 2000
679
+
680
+ cursor.execute("SELECT COUNT(*) as total_failed FROM failed_fixes")
681
+ total_failed = cursor.fetchone()["total_failed"] or 0
682
+
683
+ cursor.execute("SELECT COUNT(*) as total_lessons FROM lessons_learned")
684
+ total_lessons = cursor.fetchone()["total_lessons"] or 0
685
+
686
+ cursor.execute("SELECT error_signature, module_name, hit_count FROM healing_cache ORDER BY hit_count DESC")
687
+ top_solutions = [dict(r) for r in cursor.fetchall()]
688
+
689
+ return {
690
+ "total_solutions": total_sol,
691
+ "total_hits": total_hits,
692
+ "total_failed_fixes": total_failed,
693
+ "total_lessons_learned": total_lessons,
694
+ "estimated_tokens_saved": saved_tokens,
695
+ "solutions": top_solutions
696
+ }
697
+
698
+
699
+ def main():
700
+ global DB_FILE
701
+ parser = argparse.ArgumentParser(description="Enterprise Central Agent Memory & Self-Healing Resolver")
702
+ parser.add_argument("--local", action="store_true", help="Force local project memory_cache.db instead of Global Central Memory")
703
+ subparsers = parser.add_subparsers(dest="command")
704
+
705
+ # Query command
706
+ query_parser = subparsers.add_parser("query", help="Query solution & anti-patches for an error (Zero Token)")
707
+ query_parser.add_argument("--error", required=True, help="Error signature or keyword")
708
+ query_parser.add_argument("--module", default="GLOBAL", help="Module or Page Class name")
709
+
710
+ # Learn command (Positive Memory)
711
+ learn_parser = subparsers.add_parser("learn", help="Save verified solution into positive memory")
712
+ learn_parser.add_argument("--error", required=True, help="Error signature")
713
+ learn_parser.add_argument("--module", default="GLOBAL", help="Module or Page Class name")
714
+ learn_parser.add_argument("--symptom", required=True, help="Symptom description")
715
+ learn_parser.add_argument("--cause", required=True, help="Root cause")
716
+ learn_parser.add_argument("--patch", required=True, help="Working patch code")
717
+
718
+ # Record-Failed command (Negative Memory)
719
+ failed_parser = subparsers.add_parser("record-failed", help="Record a failed fix attempt into negative memory blacklist")
720
+ failed_parser.add_argument("--error", required=True, help="Error signature being diagnosed")
721
+ failed_parser.add_argument("--patch", required=True, help="The failed patch code or attempted action")
722
+ failed_parser.add_argument("--reason", required=True, help="Why this fix failed or regression caused")
723
+ failed_parser.add_argument("--role", default="pom-crafter", help="Agent role that attempted the fix")
724
+
725
+ # Record-Lesson command (Episodic Reflexion Memory)
726
+ lesson_record_parser = subparsers.add_parser("record-lesson", help="Record a 2-line lesson learned into episodic memory")
727
+ lesson_record_parser.add_argument("--context", default="GLOBAL", help="Page name or workflow context (e.g., 'Checkout', 'Login')")
728
+ lesson_record_parser.add_argument("--error", required=True, help="Error signature or breaking symptom")
729
+ lesson_record_parser.add_argument("--cause", default="", help="Root cause explanation")
730
+ lesson_record_parser.add_argument("--failed", default="", help="Failed attempt or anti-pattern")
731
+ lesson_record_parser.add_argument("--fix", default="", help="Successful fix pattern")
732
+ lesson_record_parser.add_argument("--lesson", required=True, help="Short lesson summary (maximum 2 lines)")
733
+
734
+ # Get-Lessons command (Episodic Reflexion Memory Query)
735
+ lesson_get_parser = subparsers.add_parser("get-lessons", help="Retrieve top 2 historical lessons learned for context")
736
+ lesson_get_parser.add_argument("--context", required=True, help="Page, flow, or error keyword to match")
737
+ lesson_get_parser.add_argument("--limit", type=int, default=2, help="Number of lessons to retrieve (default: 2)")
738
+
739
+ # Stats command
740
+ subparsers.add_parser("stats", help="View memory cache statistics (Positive & Negative)")
741
+
742
+ # Sync command
743
+ subparsers.add_parser("sync", help="Synchronize Global Central Memory with Local Project Cache")
744
+
745
+ # Init command
746
+ subparsers.add_parser("init", help="Initialize and seed database")
747
+
748
+ args = parser.parse_args()
749
+ if args.local:
750
+ DB_FILE = resolve_db_path(use_local=True)
751
+ else:
752
+ DB_FILE = resolve_db_path(use_local=False)
753
+
754
+ if args.command == "query":
755
+ sol = query_solution(args.error, args.module)
756
+ if sol:
757
+ print("==================================================")
758
+ if sol.get("groovy_patch"):
759
+ print(f"✅ [CACHE HIT - 0 TOKENS] Found pre-tested solution!")
760
+ print(f"Database Scope : {'LOCAL' if args.local else 'GLOBAL CENTRAL (~/.agent_memory)'}")
761
+ print(f"Match Strategy : {sol.get('match_type', 'EXACT')}")
762
+ print(f"Error Signature : {sol['error_signature']}")
763
+ print(f"Module Scope : {sol['module_name']}")
764
+ print(f"Root Cause : {sol['root_cause']}")
765
+ print(f"Hit Count : {sol.get('hit_count', 0)}")
766
+ print("--------------------------------------------------")
767
+ print("💡 Recommended Healing Patch:")
768
+ print(sol["groovy_patch"])
769
+ else:
770
+ print(f"⚠️ [NO VERIFIED SOLUTION YET] for error: '{args.error}'")
771
+
772
+ failed_fixes = sol.get("failed_fixes", [])
773
+ if failed_fixes:
774
+ print("--------------------------------------------------")
775
+ print(f"🚫 FORBIDDEN ANTI-PATCHES ({len(failed_fixes)} previously failed - DO NOT REPEAT):")
776
+ for idx, ff in enumerate(failed_fixes, 1):
777
+ print(f" {idx}. [FAILED ATTEMPT by {ff.get('agent_role', 'agent')}]:")
778
+ print(f" Patch : {ff.get('attempted_patch')}")
779
+ print(f" Reason: {ff.get('failure_reason')}")
780
+ print("==================================================")
781
+ else:
782
+ print(f"❌ [CACHE MISS] No cached solution found for: '{args.error}'. Triggering LLM reasoning.")
783
+ sys.exit(1)
784
+
785
+ elif args.command == "learn":
786
+ ok = learn_solution(args.error, args.module, args.symptom, args.cause, args.patch)
787
+ if ok:
788
+ print(f"✅ [POSITIVE MEMORY LEARNED] Solution for '{args.error}' ({args.module}) cached successfully.")
789
+
790
+ elif args.command == "record-failed":
791
+ ok = record_failed_fix(args.error, args.patch, args.reason, args.role)
792
+ if ok:
793
+ print(f"🚫 [NEGATIVE MEMORY RECORDED] Failed fix attempt for '{args.error}' added to blacklist.")
794
+ print(f" Agent Role : {args.role}")
795
+ print(f" Reason : {args.reason}")
796
+
797
+ elif args.command == "record-lesson":
798
+ ok = record_lesson(args.context, args.error, args.cause, args.failed, args.fix, args.lesson)
799
+ if ok:
800
+ print(f"🧠 [EPISODIC REFLEXION RECORDED] Lesson for context '{args.context}' cached successfully.")
801
+ print(f" Error Signature : {args.error}")
802
+ print(f" Lesson (2 lines): {args.lesson.strip()}")
803
+
804
+ elif args.command == "get-lessons":
805
+ lessons = query_lessons(args.context, limit=args.limit)
806
+ print("==================================================")
807
+ print(f"🧠 [EPISODIC REFLEXION MEMORY] Top {len(lessons)} Lessons for: '{args.context}'")
808
+ print("==================================================")
809
+ if not lessons:
810
+ print(" (Zero relevant lessons in cache yet. Proceeding with clean reasoning.)")
811
+ else:
812
+ for idx, l in enumerate(lessons, 1):
813
+ print(f"📌 [Lesson {idx}] Context: {l['page_context']} | Trigger: {l['error_signature']}")
814
+ print(f" 💡 Rule: {l['lesson_summary']}")
815
+ if l.get("failed_attempt") or l.get("successful_fix"):
816
+ print(f" ⚠️ Anti-Pattern: {l.get('failed_attempt') or 'N/A'}")
817
+ print(f" ✅ Proven Fix : {l.get('successful_fix') or 'N/A'}")
818
+ print("==================================================")
819
+
820
+ elif args.command == "stats":
821
+ stats = get_stats()
822
+ print("==================================================")
823
+ print(f"📊 AGENT MEMORY CACHE STATISTICS ({'LOCAL' if args.local else 'GLOBAL CENTRAL'})")
824
+ print(f"Database Path : {DB_FILE}")
825
+ print(f"Verified Solutions (✅) : {stats['total_solutions']}")
826
+ print(f"Failed Fixes Blacklist (🚫): {stats['total_failed_fixes']}")
827
+ print(f"Lessons Learned (🧠) : {stats.get('total_lessons_learned', 0)}")
828
+ print(f"Total Cache Hits : {stats['total_hits']}")
829
+ print(f"Estimated Tokens Saved : ~{stats['estimated_tokens_saved']:,} tokens")
830
+ print("--------------------------------------------------")
831
+ print("Top Solutions by Hit Count:")
832
+ for s in stats["solutions"]:
833
+ print(f" - [{s['hit_count']} hits] {s['error_signature']} ({s['module_name']})")
834
+ print("==================================================")
835
+
836
+ elif args.command == "sync":
837
+ count = sync_db()
838
+ print(f"✅ [MEMORY SYNC] Bidirectional sync completed ({count} solutions synchronized between Global & Local).")
839
+
840
+ elif args.command == "init" or args.command is None:
841
+ init_db(seed=True)
842
+ print(f"✅ Central Agent Memory initialized at: {DB_FILE}")
843
+
844
+
845
+ if __name__ == "__main__":
846
+ main()