synapse-hub 2.6.0 → 3.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 (143) hide show
  1. package/.agents/agents/failure-analyst.md +4 -0
  2. package/.agents/agents/live-explorer.md +2 -0
  3. package/.agents/agents/orchestrator.md +16 -0
  4. package/.agents/agents/pom-crafter.md +10 -0
  5. package/.agents/agents/quality-gatekeeper.md +5 -0
  6. package/.agents/agents/test-runner.md +8 -0
  7. package/.agents/rules/git-push-protocol.md +1 -1
  8. package/.agents/rules/graphify.md +2 -0
  9. package/.agents/rules/mandatory-playwright-live.md +9 -9
  10. package/.agents/rules/mobile_appium_rules.md +8 -1
  11. package/.agents/skills/actor-qa-swarm/SKILL.md +77 -0
  12. package/.agents/skills/actor-qa-swarm/examples/broker_sharding_sample.json +12 -0
  13. package/.agents/skills/agentless-qa-repair/SKILL.md +69 -0
  14. package/.agents/skills/agentless-qa-repair/examples/healing_report_sample.json +17 -0
  15. package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +1 -1
  16. package/.agents/skills/clean-pom-crafter/SKILL.md +2 -2
  17. package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +4 -4
  18. package/.agents/skills/event-stream-qa-orchestrator/SKILL.md +79 -0
  19. package/.agents/skills/event-stream-qa-orchestrator/examples/timeline_sample.jsonl +2 -0
  20. package/.agents/skills/failure-autopsy-and-healing/SKILL.md +2 -2
  21. package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +6 -21
  22. package/.agents/skills/katalon-cli-runner/SKILL.md +1 -1
  23. package/.agents/skills/lean-impact-scoreboard/SKILL.md +1 -1
  24. package/.agents/skills/mipro-prompt-compiler/SKILL.md +59 -0
  25. package/.agents/skills/mipro-prompt-compiler/examples/compiled_module_sample.json +22 -0
  26. package/.agents/skills/qa-aci-interface/SKILL.md +67 -0
  27. package/.agents/skills/qa-aci-interface/examples/surgical_edit_sample.json +12 -0
  28. package/.agents/skills/qa-memory-os/SKILL.md +80 -0
  29. package/.agents/skills/qa-memory-os/examples/archival_memory_sample.json +21 -0
  30. package/.agents/skills/repo-quality-audit/SKILL.md +1 -1
  31. package/.agents/skills/review-spec/SKILL.md +60 -0
  32. package/.agents/skills/review-spec/examples/sample_spec_review.md +33 -0
  33. package/.agents/skills/senior-code-review/SKILL.md +1 -1
  34. package/.agents/skills/synthetic-test-data-generator/SKILL.md +2 -2
  35. package/.agents/skills/technical-debt-harvester/SKILL.md +1 -1
  36. package/.agents/skills/tot-exploratory-testing/SKILL.md +59 -0
  37. package/.agents/skills/tot-exploratory-testing/examples/tot_graph_sample.json +35 -0
  38. package/.agents/skills/wcag-audit-patterns/SKILL.md +2 -2
  39. package/.agents/skills/web-agent-gym-benchmarking/SKILL.md +62 -0
  40. package/.agents/skills/web-agent-gym-benchmarking/examples/benchmark_report_sample.json +22 -0
  41. package/CLAUDE.md +2 -2
  42. package/README.en.md +70 -291
  43. package/README.md +68 -303
  44. package/docs/ARCHITECTURE.md +54 -8
  45. package/docs/CONTRIBUTING.md +1 -1
  46. package/docs/QUALITY_GATES.md +113 -0
  47. package/docs/research/EXTERNAL_AGENT_SKILLS_SURVEY.md +1062 -0
  48. package/package.json +1 -1
  49. package/scripts/package_dist.py +2 -1
  50. package/skills_manifest.json +92 -11
  51. package/synapse.config.json +1 -1
  52. package/templates/ai_bridges/CLAUDE.md +2 -2
  53. package/templates/ai_bridges/copilot-instructions.md +2 -2
  54. package/tools/TOOLS_RUNBOOK.md +1 -1
  55. package/tools/__init__.py +18 -1
  56. package/tools/_openapi_parser.py +158 -0
  57. package/tools/agent_canary.py +42 -30
  58. package/tools/agentless_patcher.py +417 -0
  59. package/tools/analyze_project.py +12 -11
  60. package/tools/api_drift_sentinel.py +4 -4
  61. package/tools/api_fuzzer.py +8 -8
  62. package/tools/auto_heal.py +23 -5
  63. package/tools/auto_heal_reflexion.py +2 -2
  64. package/tools/autogen_qa_broker.py +318 -0
  65. package/tools/browser_helpers.js +2 -2
  66. package/tools/browsergym_eval.py +236 -0
  67. package/tools/bug_reporter.py +11 -13
  68. package/tools/bump_version.py +75 -5
  69. package/tools/cache_resolver.py +78 -83
  70. package/tools/check_env.py +9 -1
  71. package/tools/check_version_sync.py +40 -3
  72. package/tools/ci_healing_hook.py +5 -7
  73. package/tools/cli.py +5 -7
  74. package/tools/config_decoupler.py +2 -3
  75. package/tools/core/__init__.py +52 -0
  76. package/tools/count_tokens.py +22 -20
  77. package/tools/dashboard_generator.py +628 -999
  78. package/tools/db_sandbox.py +69 -53
  79. package/tools/debt_harvester.py +3 -3
  80. package/tools/distill_log.py +3 -3
  81. package/tools/dspy_qa_signatures.py +3 -4
  82. package/tools/export_postman_collection.py +8 -8
  83. package/tools/flaky_detector.py +2 -2
  84. package/tools/fuzz_payloads.py +5 -5
  85. package/tools/generate_allure_report.py +1 -1
  86. package/tools/generate_jmx_plan.py +4 -4
  87. package/tools/gherkin_suite.py +1 -1
  88. package/tools/git_pre_commit_guard.py +2 -2
  89. package/tools/goldfish_review.py +2 -2
  90. package/tools/heal/__init__.py +28 -0
  91. package/tools/heal_pr_automator.py +29 -4
  92. package/tools/heuristic_locator_matcher.py +2 -2
  93. package/tools/infer_linter.py +8 -8
  94. package/tools/interview_story.py +0 -1
  95. package/tools/jtl_analyzer.py +11 -11
  96. package/tools/lint/__init__.py +30 -0
  97. package/tools/lint_ai_docs.py +42 -0
  98. package/tools/lookup_tc.py +1 -1
  99. package/tools/matrix_sharder.py +15 -13
  100. package/tools/mcp/__init__.py +16 -0
  101. package/tools/mcp_excel_server.py +2 -2
  102. package/tools/mcp_server.py +3 -4
  103. package/tools/md_to_xlsx.py +26 -26
  104. package/tools/micro_mutator.py +11 -12
  105. package/tools/mipro_assertion_tuner.py +207 -0
  106. package/tools/network_mock_shield.py +27 -27
  107. package/tools/newman_analyzer.py +2 -3
  108. package/tools/omni_emitter.py +5 -8
  109. package/tools/omni_parser.py +5 -5
  110. package/tools/omni_transpiler.py +5 -5
  111. package/tools/output_formatter.py +19 -0
  112. package/tools/perf/__init__.py +24 -0
  113. package/tools/pinpoint_failure.py +13 -1
  114. package/tools/project_state.py +4 -2
  115. package/tools/qa_event_stream.py +336 -0
  116. package/tools/repo_map.py +8 -8
  117. package/tools/report/__init__.py +32 -0
  118. package/tools/reversible_sandbox.py +5 -2
  119. package/tools/safety_firewall.py +2 -2
  120. package/tools/senior_code_review.py +2 -2
  121. package/tools/setup_project.py +10 -12
  122. package/tools/skill_collision_guard.py +3 -3
  123. package/tools/skill_vault.py +12 -3
  124. package/tools/skillspector.py +5 -7
  125. package/tools/smart_visual_masker.py +4 -6
  126. package/tools/spec_drift_sentinel.py +1 -2
  127. package/tools/swe_qa_aci.py +369 -0
  128. package/tools/synapse_qa_bench.py +3 -3
  129. package/tools/sync_agent_rules.py +18 -4
  130. package/tools/telemetry_correlator.py +3 -2
  131. package/tools/test_memory_os.py +419 -0
  132. package/tools/textgrad_mutation_optimizer.py +3 -3
  133. package/tools/tms_sync_bridge.py +5 -5
  134. package/tools/tot_exploratory_tester.py +239 -0
  135. package/tools/traffic_session_converter.py +9 -9
  136. package/tools/universal_ir.py +2 -3
  137. package/tools/update_sentinel.py +6 -9
  138. package/tools/verify_all.py +10 -0
  139. package/tools/visual_diff_canvas.py +1 -2
  140. package/tools/visual_som_helper.py +15 -13
  141. package/tools/watch_mode.py +1 -1
  142. package/tools/wcag_audit.py +22 -1
  143. package/tools/welcome_page.py +5 -6
@@ -0,0 +1,419 @@
1
+ #!/usr/bin/env python3
2
+ # SPDX-FileCopyrightText: Copyright (c) 2026 Thế Vị (@TeeVee998). All rights reserved.
3
+ # SPDX-License-Identifier: MIT
4
+ # Project: Synapse-Hub (https://github.com/TeeVee998/Synapse-Hub)
5
+
6
+ """
7
+ Hierarchical QA Test Memory OS (Letta / MemGPT Pattern)
8
+ -------------------------------------------------------
9
+ Provides a 3-tier persistent memory operating system for test automation:
10
+ 1. Core Memory: Fast key-value blocks for active environment context (AUT URL, auth token expiry, flags).
11
+ 2. Recall Memory: Sequential event log of recent test execution steps and subagent actions.
12
+ 3. Archival Memory: Long-term searchable knowledge base of locator mutations and flaky patterns,
13
+ version-scoped by AUT release (e.g. v2.1 vs v2.2) to eliminate memory rot.
14
+ 4. Memory Hygiene: Garbage collection and secret scrubbing (zero plaintext tokens/passwords).
15
+
16
+ Usage:
17
+ python tools/test_memory_os.py init --db-path .agent_memory/qa_memory.db
18
+ python tools/test_memory_os.py update-core --block auth_state --data '{"token_expiry": "..."}'
19
+ python tools/test_memory_os.py archival-insert --key LOCATOR_MUTATION --payload '{"page": "LoginPage", ...}' --aut-version "v2.1"
20
+ python tools/test_memory_os.py archival-search --query "Login submit button" --aut-version "v2.1" --json
21
+ python tools/test_memory_os.py gc --scrub-secrets --max-age-days 30
22
+ """
23
+
24
+ import os
25
+ import sys
26
+ import re
27
+ import json
28
+ import sqlite3
29
+ import time
30
+ import uuid
31
+ import difflib
32
+ import argparse
33
+ from datetime import datetime, timezone, timedelta
34
+ from typing import Dict, Any, List, Optional, Tuple
35
+
36
+ # Enforce UTF-8 stdout encoding
37
+ if hasattr(sys.stdout, 'reconfigure'):
38
+ try:
39
+ sys.stdout.reconfigure(encoding='utf-8')
40
+ sys.stderr.reconfigure(encoding='utf-8')
41
+ except Exception:
42
+ pass
43
+
44
+
45
+ SECRET_PATTERNS = [
46
+ re.compile(r'["\']?(?:password|passwd|pwd|secret|apiKey|api_key|token|bearer)["\']?\s*[:=]\s*["\']?([^"\'\s,;]{8,})["\']?', re.IGNORECASE),
47
+ re.compile(r'Bearer\s+([a-zA-Z0-9_\-\.]{15,})', re.IGNORECASE),
48
+ re.compile(r'-----BEGIN (?:RSA )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA )?PRIVATE KEY-----'),
49
+ ]
50
+
51
+
52
+ class ManagedConnection(sqlite3.Connection):
53
+ """SQLite connection that automatically commits/rollbacks and closes on context exit."""
54
+ def __exit__(self, exc_type, exc_val, exc_tb):
55
+ try:
56
+ super().__exit__(exc_type, exc_val, exc_tb)
57
+ finally:
58
+ self.close()
59
+
60
+
61
+ class TestMemoryOS:
62
+ """3-Tier Hierarchical QA Memory OS."""
63
+ __test__ = False
64
+
65
+ def __init__(self, db_path: Optional[str] = None):
66
+ if db_path:
67
+ self.db_path = db_path
68
+ else:
69
+ repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
70
+ self.db_path = os.path.join(repo_root, ".agent_memory", "qa_memory.db")
71
+
72
+ os.makedirs(os.path.dirname(os.path.abspath(self.db_path)), exist_ok=True)
73
+ self._init_db()
74
+
75
+ def _get_connection(self) -> ManagedConnection:
76
+ conn = sqlite3.connect(self.db_path, factory=ManagedConnection)
77
+ conn.row_factory = sqlite3.Row
78
+ return conn
79
+
80
+ def _init_db(self):
81
+ with self._get_connection() as conn:
82
+ # 1. Core Memory Table
83
+ conn.execute("""
84
+ CREATE TABLE IF NOT EXISTS core_memory (
85
+ block_key TEXT PRIMARY KEY,
86
+ data_json TEXT NOT NULL,
87
+ updated_at TEXT NOT NULL
88
+ )
89
+ """)
90
+ # 2. Recall Memory Table
91
+ conn.execute("""
92
+ CREATE TABLE IF NOT EXISTS recall_memory (
93
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
94
+ session_id TEXT NOT NULL,
95
+ event_type TEXT NOT NULL,
96
+ actor TEXT NOT NULL,
97
+ payload_json TEXT NOT NULL,
98
+ timestamp TEXT NOT NULL
99
+ )
100
+ """)
101
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_recall_session ON recall_memory(session_id, timestamp)")
102
+
103
+ # 3. Archival Memory Table
104
+ conn.execute("""
105
+ CREATE TABLE IF NOT EXISTS archival_memory (
106
+ id TEXT PRIMARY KEY,
107
+ key TEXT NOT NULL,
108
+ aut_version TEXT DEFAULT 'latest',
109
+ payload_json TEXT NOT NULL,
110
+ tags TEXT DEFAULT '',
111
+ text_content TEXT NOT NULL,
112
+ created_at TEXT NOT NULL
113
+ )
114
+ """)
115
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_archival_key_ver ON archival_memory(key, aut_version)")
116
+
117
+ # ----------------- Tier 1: Core Memory -----------------
118
+
119
+ def update_core(self, block_key: str, data: Any) -> Dict[str, Any]:
120
+ """Updates or inserts a core memory block."""
121
+ data_str = json.dumps(data) if not isinstance(data, str) else data
122
+ now = datetime.now(timezone.utc).isoformat()
123
+ with self._get_connection() as conn:
124
+ conn.execute("""
125
+ INSERT INTO core_memory (block_key, data_json, updated_at)
126
+ VALUES (?, ?, ?)
127
+ ON CONFLICT(block_key) DO UPDATE SET
128
+ data_json=excluded.data_json,
129
+ updated_at=excluded.updated_at
130
+ """, (block_key, data_str, now))
131
+
132
+ return {
133
+ "status": "PASS",
134
+ "block_key": block_key,
135
+ "updated_at": now
136
+ }
137
+
138
+ def get_core(self, block_key: str) -> Optional[Dict[str, Any]]:
139
+ """Retrieves a core memory block."""
140
+ with self._get_connection() as conn:
141
+ row = conn.execute("SELECT data_json, updated_at FROM core_memory WHERE block_key = ?", (block_key,)).fetchone()
142
+ if row:
143
+ try:
144
+ parsed = json.loads(row["data_json"])
145
+ except Exception:
146
+ parsed = row["data_json"]
147
+ return {
148
+ "block_key": block_key,
149
+ "data": parsed,
150
+ "updated_at": row["updated_at"]
151
+ }
152
+ return None
153
+
154
+ def list_core(self) -> List[Dict[str, Any]]:
155
+ """Lists all core memory blocks."""
156
+ with self._get_connection() as conn:
157
+ rows = conn.execute("SELECT block_key, data_json, updated_at FROM core_memory ORDER BY block_key").fetchall()
158
+ results = []
159
+ for r in rows:
160
+ try:
161
+ parsed = json.loads(r["data_json"])
162
+ except Exception:
163
+ parsed = r["data_json"]
164
+ results.append({"block_key": r["block_key"], "data": parsed, "updated_at": r["updated_at"]})
165
+ return results
166
+
167
+ # ----------------- Tier 2: Recall Memory -----------------
168
+
169
+ def recall_insert(self, session_id: str, event_type: str, actor: str, payload: Any) -> int:
170
+ """Inserts an event into recall memory log."""
171
+ payload_str = json.dumps(payload) if not isinstance(payload, str) else payload
172
+ now = datetime.now(timezone.utc).isoformat()
173
+ with self._get_connection() as conn:
174
+ cursor = conn.execute("""
175
+ INSERT INTO recall_memory (session_id, event_type, actor, payload_json, timestamp)
176
+ VALUES (?, ?, ?, ?, ?)
177
+ """, (session_id, event_type, actor, payload_str, now))
178
+ return cursor.lastrowid or 0
179
+
180
+ def recall_log(self, session_id: str, limit: int = 50) -> List[Dict[str, Any]]:
181
+ """Retrieves recent sequential steps for a session."""
182
+ with self._get_connection() as conn:
183
+ rows = conn.execute("""
184
+ SELECT id, session_id, event_type, actor, payload_json, timestamp
185
+ FROM recall_memory
186
+ WHERE session_id = ?
187
+ ORDER BY id ASC
188
+ LIMIT ?
189
+ """, (session_id, limit)).fetchall()
190
+ events = []
191
+ for r in rows:
192
+ try:
193
+ payload = json.loads(r["payload_json"])
194
+ except Exception:
195
+ payload = r["payload_json"]
196
+ events.append({
197
+ "id": r["id"],
198
+ "session_id": r["session_id"],
199
+ "event_type": r["event_type"],
200
+ "actor": r["actor"],
201
+ "payload": payload,
202
+ "timestamp": r["timestamp"]
203
+ })
204
+ return events
205
+
206
+ # ----------------- Tier 3: Archival Memory -----------------
207
+
208
+ def archival_insert(self, key: str, payload: Any, aut_version: str = "latest", tags: str = "") -> str:
209
+ """Inserts long-term searchable knowledge into archival memory."""
210
+ payload_str = json.dumps(payload) if not isinstance(payload, str) else payload
211
+ now = datetime.now(timezone.utc).isoformat()
212
+ mem_id = f"mem_arch_{int(time.time() * 1000) % 10000000}_{uuid.uuid4().hex[:6]}"
213
+
214
+ # Flatten text content for semantic/fuzzy search
215
+ text_content = f"{key} {aut_version} {tags} {payload_str}".lower()
216
+
217
+ with self._get_connection() as conn:
218
+ conn.execute("""
219
+ INSERT INTO archival_memory (id, key, aut_version, payload_json, tags, text_content, created_at)
220
+ VALUES (?, ?, ?, ?, ?, ?, ?)
221
+ """, (mem_id, key, aut_version, payload_str, tags, text_content, now))
222
+
223
+ return mem_id
224
+
225
+ def archival_search(self, query: str, aut_version: Optional[str] = None, top_k: int = 3) -> Dict[str, Any]:
226
+ """Searches archival memory with semantic token matching and fuzzy ratio."""
227
+ query_lower = query.lower()
228
+ tokens = set(re.findall(r'\b[a-z0-9_]{3,}\b', query_lower))
229
+
230
+ with self._get_connection() as conn:
231
+ if aut_version:
232
+ rows = conn.execute(
233
+ "SELECT id, key, aut_version, payload_json, tags, text_content, created_at FROM archival_memory WHERE aut_version = ?",
234
+ (aut_version,)
235
+ ).fetchall()
236
+ else:
237
+ rows = conn.execute(
238
+ "SELECT id, key, aut_version, payload_json, tags, text_content, created_at FROM archival_memory"
239
+ ).fetchall()
240
+
241
+ scored = []
242
+ for r in rows:
243
+ text = r["text_content"]
244
+ # 1. Token overlap score
245
+ text_tokens = set(re.findall(r'\b[a-z0-9_]{3,}\b', text))
246
+ if tokens:
247
+ token_score = len(tokens.intersection(text_tokens)) / len(tokens)
248
+ else:
249
+ token_score = 0.0
250
+
251
+ # 2. Fuzzy sequence similarity
252
+ fuzzy_ratio = difflib.SequenceMatcher(None, query_lower, text[:200]).ratio()
253
+
254
+ # Combined score: 60% token overlap + 40% fuzzy match
255
+ score = round((0.6 * token_score) + (0.4 * fuzzy_ratio), 3)
256
+
257
+ if score > 0.15 or (tokens and tokens.intersection(text_tokens)):
258
+ try:
259
+ payload = json.loads(r["payload_json"])
260
+ except Exception:
261
+ payload = r["payload_json"]
262
+
263
+ scored.append({
264
+ "id": r["id"],
265
+ "similarity_score": score,
266
+ "key": r["key"],
267
+ "aut_version": r["aut_version"],
268
+ "timestamp": r["created_at"],
269
+ "payload": payload
270
+ })
271
+
272
+ scored.sort(key=lambda x: x["similarity_score"], reverse=True)
273
+ results = scored[:top_k]
274
+
275
+ return {
276
+ "status": "PASS",
277
+ "query": query,
278
+ "aut_version": aut_version or "all",
279
+ "matches_found": len(results),
280
+ "results": results
281
+ }
282
+
283
+ # ----------------- Hygiene & Secret Scrubbing -----------------
284
+
285
+ def scrub_string(self, text: str) -> Tuple[str, int]:
286
+ """Masks detected secrets in text."""
287
+ scrubbed = text
288
+ count = 0
289
+ for pat in SECRET_PATTERNS:
290
+ matches = list(pat.finditer(scrubbed))
291
+ if matches:
292
+ count += len(matches)
293
+ for m in matches:
294
+ val = m.group(0)
295
+ scrubbed = scrubbed.replace(val, "[MASKED_SECRET]")
296
+ return scrubbed, count
297
+
298
+ def gc(self, scrub_secrets: bool = True, max_age_days: int = 30) -> Dict[str, Any]:
299
+ """Performs garbage collection and secret scrubbing."""
300
+ scrub_count = 0
301
+ deleted_recall = 0
302
+
303
+ with self._get_connection() as conn:
304
+ # 1. Clean old recall logs
305
+ cutoff = (datetime.now(timezone.utc) - timedelta(days=max_age_days)).isoformat()
306
+ cur = conn.execute("DELETE FROM recall_memory WHERE timestamp < ?", (cutoff,))
307
+ deleted_recall = cur.rowcount
308
+
309
+ # 2. Scrub secrets in core memory
310
+ if scrub_secrets:
311
+ rows = conn.execute("SELECT block_key, data_json FROM core_memory").fetchall()
312
+ for r in rows:
313
+ new_val, c = self.scrub_string(r["data_json"])
314
+ if c > 0:
315
+ scrub_count += c
316
+ conn.execute("UPDATE core_memory SET data_json = ? WHERE block_key = ?", (new_val, r["block_key"]))
317
+
318
+ # Scrub archival memory
319
+ a_rows = conn.execute("SELECT id, payload_json, text_content FROM archival_memory").fetchall()
320
+ for ar in a_rows:
321
+ new_p, cp = self.scrub_string(ar["payload_json"])
322
+ new_t, ct = self.scrub_string(ar["text_content"])
323
+ if cp > 0 or ct > 0:
324
+ scrub_count += (cp + ct)
325
+ conn.execute("UPDATE archival_memory SET payload_json = ?, text_content = ? WHERE id = ?", (new_p, new_t, ar["id"]))
326
+
327
+ # Database file size check
328
+ db_size_mb = 0.0
329
+ if os.path.exists(self.db_path):
330
+ db_size_mb = round(os.path.getsize(self.db_path) / (1024 * 1024), 2)
331
+
332
+ return {
333
+ "status": "PASS",
334
+ "db_path": self.db_path,
335
+ "db_size_mb": db_size_mb,
336
+ "max_size_limit_mb": 50.0,
337
+ "size_status": "PASS" if db_size_mb <= 50.0 else "WARNING_OVERSIZED",
338
+ "secrets_scrubbed": scrub_count,
339
+ "expired_recall_records_deleted": deleted_recall
340
+ }
341
+
342
+
343
+ def main():
344
+ parser = argparse.ArgumentParser(description="Hierarchical QA Test Memory OS (Letta Pattern)")
345
+ parser.add_argument("--db-path", help="Path to SQLite database file")
346
+ subparsers = parser.add_subparsers(dest="subcommand", help="Subcommand to execute")
347
+
348
+ # init
349
+ subparsers.add_parser("init", help="Initialize memory OS database")
350
+
351
+ # update-core
352
+ uc_parser = subparsers.add_parser("update-core", help="Update core memory block")
353
+ uc_parser.add_argument("--block", required=True, help="Core memory block key")
354
+ uc_parser.add_argument("--data", required=True, help="JSON or text payload")
355
+
356
+ # get-core
357
+ gc_parser = subparsers.add_parser("get-core", help="Retrieve core memory block")
358
+ gc_parser.add_argument("--block", required=True, help="Core memory block key")
359
+
360
+ # archival-insert
361
+ ai_parser = subparsers.add_parser("archival-insert", help="Insert long-term knowledge into Archival Memory")
362
+ ai_parser.add_argument("--key", required=True, help="Knowledge key (e.g. LOCATOR_MUTATION)")
363
+ ai_parser.add_argument("--payload", required=True, help="JSON data payload")
364
+ ai_parser.add_argument("--aut-version", default="latest", help="AUT release version (e.g. v2.1)")
365
+ ai_parser.add_argument("--tags", default="", help="Comma-separated search tags")
366
+
367
+ # archival-search
368
+ as_parser = subparsers.add_parser("archival-search", help="Search archival memory by query")
369
+ as_parser.add_argument("--query", required=True, help="Search query string")
370
+ as_parser.add_argument("--aut-version", help="Filter by AUT release version")
371
+ as_parser.add_argument("--top-k", type=int, default=3, help="Max results to return")
372
+ as_parser.add_argument("--json", action="store_true", help="Print JSON output")
373
+
374
+ # gc
375
+ clean_parser = subparsers.add_parser("gc", help="Garbage collection and secret scrubbing")
376
+ clean_parser.add_argument("--scrub-secrets", action="store_true", default=True, help="Scrub sensitive credentials")
377
+ clean_parser.add_argument("--max-age-days", type=int, default=30, help="Max age for recall logs")
378
+
379
+ args = parser.parse_args()
380
+ mem = TestMemoryOS(db_path=args.db_path)
381
+
382
+ if args.subcommand == "init":
383
+ print(json.dumps({"status": "PASS", "db_path": mem.db_path, "initialized": True}, indent=2))
384
+
385
+ elif args.subcommand == "update-core":
386
+ try:
387
+ parsed_data = json.loads(args.data)
388
+ except Exception:
389
+ parsed_data = args.data
390
+ res = mem.update_core(args.block, parsed_data)
391
+ print(json.dumps(res, indent=2))
392
+
393
+ elif args.subcommand == "get-core":
394
+ res = mem.get_core(args.block)
395
+ print(json.dumps(res or {"status": "NOT_FOUND", "block": args.block}, indent=2))
396
+
397
+ elif args.subcommand == "archival-insert":
398
+ try:
399
+ parsed_payload = json.loads(args.payload)
400
+ except Exception:
401
+ parsed_payload = args.payload
402
+ mid = mem.archival_insert(args.key, parsed_payload, aut_version=args.aut_version, tags=args.tags)
403
+ print(json.dumps({"status": "PASS", "id": mid, "key": args.key, "aut_version": args.aut_version}, indent=2))
404
+
405
+ elif args.subcommand == "archival-search":
406
+ res = mem.archival_search(args.query, aut_version=args.aut_version, top_k=args.top_k)
407
+ print(json.dumps(res, indent=2))
408
+
409
+ elif args.subcommand == "gc":
410
+ res = mem.gc(scrub_secrets=args.scrub_secrets, max_age_days=args.max_age_days)
411
+ print(json.dumps(res, indent=2))
412
+
413
+ else:
414
+ parser.print_help()
415
+ sys.exit(1)
416
+
417
+
418
+ if __name__ == "__main__":
419
+ main()
@@ -20,7 +20,7 @@ import sys
20
20
  import re
21
21
  import json
22
22
  import argparse
23
- from typing import List, Dict, Any, Tuple, Optional
23
+ from typing import List, Dict, Any, Tuple
24
24
 
25
25
  # Enforce UTF-8 stdout encoding for Windows console compatibility
26
26
  if hasattr(sys.stdout, 'reconfigure'):
@@ -33,10 +33,10 @@ if hasattr(sys.stdout, 'reconfigure'):
33
33
  SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
34
34
  sys.path.insert(0, SCRIPT_DIR)
35
35
 
36
- import micro_mutator
36
+ import micro_mutator # noqa: E402
37
37
 
38
38
  try:
39
- import textgrad
39
+ import textgrad # noqa: F401, E402
40
40
  TEXTGRAD_AVAILABLE = True
41
41
  except ImportError:
42
42
  TEXTGRAD_AVAILABLE = False
@@ -78,7 +78,7 @@ class TMSBridge:
78
78
  "issue_type": "Story",
79
79
  "status": "Ready for QA",
80
80
  "assignee": self.user_email,
81
- "description": f"As an Enterprise Accountant, I want to reconcile invoices matching ERP records to ensure financial accuracy.",
81
+ "description": "As an Enterprise Accountant, I want to reconcile invoices matching ERP records to ensure financial accuracy.",
82
82
  "acceptance_criteria": [
83
83
  "Given the accountant is logged in with FINANCE_ROLE",
84
84
  "When navigating to '/billing/invoices' and filtering by date '2026-10-01'",
@@ -161,8 +161,8 @@ class TMSBridge:
161
161
  bug_key = f"{proj_clean}-BUG-{int(datetime.now().timestamp()) % 10000}"
162
162
 
163
163
  repro = reproduction_steps or [
164
- f"1. Launch headless browser session via Playwright Live.",
165
- f"2. Navigate to target application flow under test.",
164
+ "1. Launch headless browser session via Playwright Live.",
165
+ "2. Navigate to target application flow under test.",
166
166
  f"3. Execute test action sequence for '{test_case_id}'.",
167
167
  f"4. Observe failure: {error_message}"
168
168
  ]
@@ -299,9 +299,9 @@ def main():
299
299
  if args.json:
300
300
  print(json.dumps(story, indent=2))
301
301
  else:
302
- print(f"\n======================================================================")
302
+ print("\n======================================================================")
303
303
  print(f" ENTERPRISE ALM STORY PULL: [{story.get('issue_key')}]")
304
- print(f"======================================================================")
304
+ print("======================================================================")
305
305
  print(f" Summary : {story.get('summary')}")
306
306
  print(f" Status : {story.get('status')} | Assignee: {story.get('assignee')}")
307
307
  print(f" Source : {story.get('source')}\n")