@timmeck/brain 1.9.0 → 2.1.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 (253) hide show
  1. package/README.md +33 -0
  2. package/brain.log +3876 -0
  3. package/{src/cli/commands/dashboard.ts → dashboard.html} +694 -807
  4. package/dist/api/server.d.ts +4 -18
  5. package/dist/api/server.js +4 -173
  6. package/dist/api/server.js.map +1 -1
  7. package/dist/brain.d.ts +2 -0
  8. package/dist/brain.js +15 -4
  9. package/dist/brain.js.map +1 -1
  10. package/dist/cli/colors.d.ts +4 -25
  11. package/dist/cli/colors.js +3 -89
  12. package/dist/cli/colors.js.map +1 -1
  13. package/dist/cli/commands/dashboard.js +21 -2
  14. package/dist/cli/commands/dashboard.js.map +1 -1
  15. package/dist/cli/commands/peers.d.ts +2 -0
  16. package/dist/cli/commands/peers.js +38 -0
  17. package/dist/cli/commands/peers.js.map +1 -0
  18. package/dist/cli/commands/status.js +0 -1
  19. package/dist/cli/commands/status.js.map +1 -1
  20. package/dist/config.js +2 -29
  21. package/dist/config.js.map +1 -1
  22. package/dist/db/connection.d.ts +1 -2
  23. package/dist/db/connection.js +1 -18
  24. package/dist/db/connection.js.map +1 -1
  25. package/dist/index.js +3 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/ipc/__tests__/protocol.test.d.ts +1 -0
  28. package/dist/ipc/__tests__/protocol.test.js +117 -0
  29. package/dist/ipc/__tests__/protocol.test.js.map +1 -0
  30. package/dist/ipc/client.d.ts +1 -16
  31. package/dist/ipc/client.js +1 -100
  32. package/dist/ipc/client.js.map +1 -1
  33. package/dist/ipc/protocol.d.ts +1 -8
  34. package/dist/ipc/protocol.js +1 -28
  35. package/dist/ipc/protocol.js.map +1 -1
  36. package/dist/ipc/router.d.ts +2 -0
  37. package/dist/ipc/router.js +30 -0
  38. package/dist/ipc/router.js.map +1 -1
  39. package/dist/ipc/server.d.ts +1 -22
  40. package/dist/ipc/server.js +1 -163
  41. package/dist/ipc/server.js.map +1 -1
  42. package/dist/learning/confidence-scorer.d.ts +2 -5
  43. package/dist/learning/confidence-scorer.js +4 -19
  44. package/dist/learning/confidence-scorer.js.map +1 -1
  45. package/dist/learning/decay.js +2 -3
  46. package/dist/learning/decay.js.map +1 -1
  47. package/dist/learning/learning-engine.d.ts +2 -5
  48. package/dist/learning/learning-engine.js +3 -15
  49. package/dist/learning/learning-engine.js.map +1 -1
  50. package/dist/mcp/http-server.d.ts +1 -7
  51. package/dist/mcp/http-server.js +6 -117
  52. package/dist/mcp/http-server.js.map +1 -1
  53. package/dist/mcp/server.js +5 -61
  54. package/dist/mcp/server.js.map +1 -1
  55. package/dist/mcp/tools.js +36 -0
  56. package/dist/mcp/tools.js.map +1 -1
  57. package/dist/parsing/parsers/compiler.js +1 -1
  58. package/dist/parsing/parsers/compiler.js.map +1 -1
  59. package/dist/research/research-engine.d.ts +2 -6
  60. package/dist/research/research-engine.js +3 -23
  61. package/dist/research/research-engine.js.map +1 -1
  62. package/dist/services/synapse.service.d.ts +3 -3
  63. package/dist/signals/__tests__/fingerprint.test.d.ts +1 -0
  64. package/dist/signals/__tests__/fingerprint.test.js +118 -0
  65. package/dist/signals/__tests__/fingerprint.test.js.map +1 -0
  66. package/dist/synapses/activation.d.ts +3 -13
  67. package/dist/synapses/activation.js +2 -49
  68. package/dist/synapses/activation.js.map +1 -1
  69. package/dist/synapses/decay.d.ts +2 -11
  70. package/dist/synapses/decay.js +2 -26
  71. package/dist/synapses/decay.js.map +1 -1
  72. package/dist/synapses/hebbian.d.ts +2 -13
  73. package/dist/synapses/hebbian.js +2 -35
  74. package/dist/synapses/hebbian.js.map +1 -1
  75. package/dist/synapses/pathfinder.d.ts +2 -14
  76. package/dist/synapses/pathfinder.js +2 -49
  77. package/dist/synapses/pathfinder.js.map +1 -1
  78. package/dist/synapses/synapse-manager.d.ts +7 -23
  79. package/dist/synapses/synapse-manager.js +6 -63
  80. package/dist/synapses/synapse-manager.js.map +1 -1
  81. package/dist/types/ipc.types.d.ts +1 -11
  82. package/dist/utils/__tests__/hash.test.d.ts +1 -0
  83. package/dist/utils/__tests__/hash.test.js +32 -0
  84. package/dist/utils/__tests__/hash.test.js.map +1 -0
  85. package/dist/utils/__tests__/paths.test.d.ts +1 -0
  86. package/dist/utils/__tests__/paths.test.js +75 -0
  87. package/dist/utils/__tests__/paths.test.js.map +1 -0
  88. package/dist/utils/events.d.ts +4 -8
  89. package/dist/utils/events.js +2 -14
  90. package/dist/utils/events.js.map +1 -1
  91. package/dist/utils/hash.d.ts +1 -1
  92. package/dist/utils/hash.js +1 -4
  93. package/dist/utils/hash.js.map +1 -1
  94. package/dist/utils/logger.d.ts +3 -2
  95. package/dist/utils/logger.js +8 -35
  96. package/dist/utils/logger.js.map +1 -1
  97. package/dist/utils/paths.d.ts +2 -1
  98. package/dist/utils/paths.js +4 -13
  99. package/dist/utils/paths.js.map +1 -1
  100. package/eslint.config.js +14 -0
  101. package/package.json +56 -49
  102. package/BRAIN_PLAN.md +0 -3324
  103. package/reddit_post.md +0 -45
  104. package/src/api/server.ts +0 -395
  105. package/src/brain.ts +0 -313
  106. package/src/cli/colors.ts +0 -116
  107. package/src/cli/commands/config.ts +0 -169
  108. package/src/cli/commands/doctor.ts +0 -124
  109. package/src/cli/commands/explain.ts +0 -83
  110. package/src/cli/commands/export.ts +0 -31
  111. package/src/cli/commands/import.ts +0 -199
  112. package/src/cli/commands/insights.ts +0 -65
  113. package/src/cli/commands/learn.ts +0 -24
  114. package/src/cli/commands/modules.ts +0 -53
  115. package/src/cli/commands/network.ts +0 -67
  116. package/src/cli/commands/projects.ts +0 -42
  117. package/src/cli/commands/query.ts +0 -120
  118. package/src/cli/commands/start.ts +0 -105
  119. package/src/cli/commands/status.ts +0 -75
  120. package/src/cli/commands/stop.ts +0 -34
  121. package/src/cli/ipc-helper.ts +0 -22
  122. package/src/cli/update-check.ts +0 -63
  123. package/src/code/analyzer.ts +0 -117
  124. package/src/code/fingerprint.ts +0 -87
  125. package/src/code/matcher.ts +0 -129
  126. package/src/code/parsers/generic.ts +0 -29
  127. package/src/code/parsers/python.ts +0 -54
  128. package/src/code/parsers/typescript.ts +0 -65
  129. package/src/code/registry.ts +0 -60
  130. package/src/code/scorer.ts +0 -120
  131. package/src/config.ts +0 -135
  132. package/src/dashboard/server.ts +0 -142
  133. package/src/db/connection.ts +0 -22
  134. package/src/db/migrations/001_core_schema.ts +0 -120
  135. package/src/db/migrations/002_learning_schema.ts +0 -38
  136. package/src/db/migrations/003_code_schema.ts +0 -53
  137. package/src/db/migrations/004_synapses_schema.ts +0 -57
  138. package/src/db/migrations/005_fts_indexes.ts +0 -78
  139. package/src/db/migrations/006_synapses_phase3.ts +0 -17
  140. package/src/db/migrations/007_feedback.ts +0 -13
  141. package/src/db/migrations/008_git_integration.ts +0 -38
  142. package/src/db/migrations/009_embeddings.ts +0 -8
  143. package/src/db/migrations/index.ts +0 -70
  144. package/src/db/repositories/antipattern.repository.ts +0 -66
  145. package/src/db/repositories/code-module.repository.ts +0 -142
  146. package/src/db/repositories/error.repository.ts +0 -189
  147. package/src/db/repositories/insight.repository.ts +0 -99
  148. package/src/db/repositories/notification.repository.ts +0 -66
  149. package/src/db/repositories/project.repository.ts +0 -93
  150. package/src/db/repositories/rule.repository.ts +0 -108
  151. package/src/db/repositories/solution.repository.ts +0 -154
  152. package/src/db/repositories/synapse.repository.ts +0 -163
  153. package/src/db/repositories/terminal.repository.ts +0 -101
  154. package/src/embeddings/engine.ts +0 -238
  155. package/src/hooks/post-tool-use.ts +0 -92
  156. package/src/hooks/post-write.ts +0 -129
  157. package/src/index.ts +0 -63
  158. package/src/ipc/client.ts +0 -118
  159. package/src/ipc/protocol.ts +0 -35
  160. package/src/ipc/router.ts +0 -133
  161. package/src/ipc/server.ts +0 -176
  162. package/src/learning/confidence-scorer.ts +0 -80
  163. package/src/learning/decay.ts +0 -46
  164. package/src/learning/learning-engine.ts +0 -170
  165. package/src/learning/pattern-extractor.ts +0 -90
  166. package/src/learning/rule-generator.ts +0 -74
  167. package/src/main.rs:10:5 +0 -0
  168. package/src/matching/error-matcher.ts +0 -166
  169. package/src/matching/fingerprint.ts +0 -34
  170. package/src/matching/similarity.ts +0 -61
  171. package/src/matching/tfidf.ts +0 -74
  172. package/src/matching/tokenizer.ts +0 -41
  173. package/src/mcp/auto-detect.ts +0 -93
  174. package/src/mcp/http-server.ts +0 -140
  175. package/src/mcp/server.ts +0 -73
  176. package/src/mcp/tools.ts +0 -328
  177. package/src/parsing/error-parser.ts +0 -28
  178. package/src/parsing/parsers/compiler.ts +0 -93
  179. package/src/parsing/parsers/generic.ts +0 -28
  180. package/src/parsing/parsers/go.ts +0 -97
  181. package/src/parsing/parsers/node.ts +0 -69
  182. package/src/parsing/parsers/python.ts +0 -62
  183. package/src/parsing/parsers/rust.ts +0 -50
  184. package/src/parsing/parsers/shell.ts +0 -42
  185. package/src/parsing/types.ts +0 -47
  186. package/src/research/gap-analyzer.ts +0 -135
  187. package/src/research/insight-generator.ts +0 -123
  188. package/src/research/research-engine.ts +0 -116
  189. package/src/research/synergy-detector.ts +0 -126
  190. package/src/research/template-extractor.ts +0 -130
  191. package/src/research/trend-analyzer.ts +0 -127
  192. package/src/services/analytics.service.ts +0 -226
  193. package/src/services/code.service.ts +0 -271
  194. package/src/services/error.service.ts +0 -266
  195. package/src/services/git.service.ts +0 -132
  196. package/src/services/notification.service.ts +0 -41
  197. package/src/services/prevention.service.ts +0 -159
  198. package/src/services/research.service.ts +0 -98
  199. package/src/services/solution.service.ts +0 -174
  200. package/src/services/synapse.service.ts +0 -59
  201. package/src/services/terminal.service.ts +0 -81
  202. package/src/synapses/activation.ts +0 -80
  203. package/src/synapses/decay.ts +0 -38
  204. package/src/synapses/hebbian.ts +0 -69
  205. package/src/synapses/pathfinder.ts +0 -81
  206. package/src/synapses/synapse-manager.ts +0 -113
  207. package/src/types/code.types.ts +0 -52
  208. package/src/types/config.types.ts +0 -103
  209. package/src/types/error.types.ts +0 -67
  210. package/src/types/ipc.types.ts +0 -8
  211. package/src/types/mcp.types.ts +0 -53
  212. package/src/types/research.types.ts +0 -28
  213. package/src/types/solution.types.ts +0 -30
  214. package/src/types/synapse.types.ts +0 -50
  215. package/src/utils/events.ts +0 -45
  216. package/src/utils/hash.ts +0 -5
  217. package/src/utils/logger.ts +0 -48
  218. package/src/utils/paths.ts +0 -19
  219. package/tests/e2e/test_code_intelligence.py +0 -1015
  220. package/tests/e2e/test_error_memory.py +0 -451
  221. package/tests/e2e/test_full_integration.py +0 -534
  222. package/tests/fixtures/code-modules/modules.ts +0 -83
  223. package/tests/fixtures/errors/go.ts +0 -9
  224. package/tests/fixtures/errors/node.ts +0 -24
  225. package/tests/fixtures/errors/python.ts +0 -21
  226. package/tests/fixtures/errors/rust.ts +0 -25
  227. package/tests/fixtures/errors/shell.ts +0 -15
  228. package/tests/fixtures/solutions/solutions.ts +0 -27
  229. package/tests/helpers/setup-db.ts +0 -52
  230. package/tests/integration/code-flow.test.ts +0 -86
  231. package/tests/integration/error-flow.test.ts +0 -83
  232. package/tests/integration/ipc-flow.test.ts +0 -166
  233. package/tests/integration/learning-cycle.test.ts +0 -82
  234. package/tests/integration/synapse-flow.test.ts +0 -117
  235. package/tests/unit/code/analyzer.test.ts +0 -58
  236. package/tests/unit/code/fingerprint.test.ts +0 -51
  237. package/tests/unit/code/scorer.test.ts +0 -55
  238. package/tests/unit/learning/confidence-scorer.test.ts +0 -60
  239. package/tests/unit/learning/decay.test.ts +0 -45
  240. package/tests/unit/learning/pattern-extractor.test.ts +0 -50
  241. package/tests/unit/matching/error-matcher.test.ts +0 -69
  242. package/tests/unit/matching/fingerprint.test.ts +0 -47
  243. package/tests/unit/matching/similarity.test.ts +0 -65
  244. package/tests/unit/matching/tfidf.test.ts +0 -71
  245. package/tests/unit/matching/tokenizer.test.ts +0 -83
  246. package/tests/unit/parsing/parsers.test.ts +0 -113
  247. package/tests/unit/research/gap-analyzer.test.ts +0 -45
  248. package/tests/unit/research/trend-analyzer.test.ts +0 -45
  249. package/tests/unit/synapses/activation.test.ts +0 -80
  250. package/tests/unit/synapses/decay.test.ts +0 -27
  251. package/tests/unit/synapses/hebbian.test.ts +0 -96
  252. package/tests/unit/synapses/pathfinder.test.ts +0 -72
  253. package/tsconfig.json +0 -18
@@ -1,534 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Brain v1.8.1 — Full System Integration Test
4
- Tests EVERY remaining endpoint, CLI, MCP HTTP, SSE, error handling, and performance.
5
- ~80 assertions covering the complete system.
6
-
7
- Run AFTER test_error_memory.py and test_code_intelligence.py for richer data.
8
- """
9
-
10
- import sys
11
- import time
12
- import json
13
- import uuid
14
- import subprocess
15
- import threading
16
- import httpx
17
-
18
- BASE = "http://localhost:7777/api/v1"
19
- MCP_BASE = "http://localhost:7778"
20
- PASS = 0
21
- FAIL = 0
22
- ERRORS: list[str] = []
23
- PERF: list[tuple[str, float]] = []
24
-
25
-
26
- def check(condition: bool, label: str) -> bool:
27
- global PASS, FAIL
28
- if condition:
29
- PASS += 1
30
- print(f" \033[32mPASS\033[0m {label}")
31
- else:
32
- FAIL += 1
33
- ERRORS.append(label)
34
- print(f" \033[31mFAIL\033[0m {label}")
35
- return condition
36
-
37
-
38
- def timed_get(path: str, params: dict | None = None, base: str = BASE) -> httpx.Response:
39
- t0 = time.perf_counter()
40
- r = httpx.get(f"{base}{path}", params=params, timeout=15)
41
- dt = (time.perf_counter() - t0) * 1000
42
- PERF.append((f"GET {path}", dt))
43
- return r
44
-
45
-
46
- def timed_post(path: str, json_data: dict | list | None = None, base: str = BASE) -> httpx.Response:
47
- t0 = time.perf_counter()
48
- r = httpx.post(f"{base}{path}", json=json_data or {}, timeout=15)
49
- dt = (time.perf_counter() - t0) * 1000
50
- PERF.append((f"POST {path}", dt))
51
- return r
52
-
53
-
54
- def post(path: str, json_data: dict | list | None = None, base: str = BASE) -> httpx.Response:
55
- return httpx.post(f"{base}{path}", json=json_data or {}, timeout=15)
56
-
57
-
58
- def get(path: str, params: dict | None = None, base: str = BASE) -> httpx.Response:
59
- return httpx.get(f"{base}{path}", params=params, timeout=15)
60
-
61
-
62
- def section(title: str) -> None:
63
- print(f"\n{'-' * 50}")
64
- print(f" {title}")
65
- print(f"{'-' * 50}")
66
-
67
-
68
- def main() -> int:
69
- print("\n" + "=" * 60)
70
- print(" BRAIN E2E TEST: Full System Integration")
71
- print("=" * 60)
72
-
73
- # ══════════════════════════════════════════════════════════
74
- # Section A: REST Infrastructure
75
- # ══════════════════════════════════════════════════════════
76
- section("A: REST Infrastructure")
77
-
78
- # A1: Health check
79
- r = timed_get("/health")
80
- check(r.status_code == 200, "Health endpoint returns 200")
81
- health = r.json()
82
- check(health.get("status") == "ok", "Health status is 'ok'")
83
- check("timestamp" in health, "Health includes timestamp")
84
-
85
- # A2: Methods listing
86
- r = timed_get("/methods")
87
- check(r.status_code == 200, "Methods endpoint returns 200")
88
- methods = r.json().get("methods", [])
89
- check(isinstance(methods, list) and len(methods) >= 30, f"Listed {len(methods)} methods (expect 30+)")
90
-
91
- # A3: Single RPC call
92
- r = timed_post("/rpc", {"method": "analytics.summary", "params": {}})
93
- check(r.status_code == 200, "Single RPC returns 200")
94
- check("result" in r.json(), "RPC response has 'result' key")
95
-
96
- # A4: Batch RPC call
97
- batch = [
98
- {"method": "analytics.summary", "params": {}, "id": 1},
99
- {"method": "synapse.stats", "params": {}, "id": 2},
100
- {"method": "project.list", "params": {}, "id": 3},
101
- ]
102
- r = timed_post("/rpc", batch)
103
- check(r.status_code == 200, "Batch RPC returns 200")
104
- results = r.json()
105
- check(isinstance(results, list) and len(results) == 3, f"Batch returned {len(results)} results")
106
- check(all("result" in item or "error" in item for item in results), "All batch items have result or error")
107
-
108
- # A5: RPC with unknown method
109
- r = post("/rpc", {"method": "nonexistent.method", "params": {}})
110
- check(r.status_code == 400, "Unknown RPC method returns 400")
111
- check("error" in r.json(), "Unknown method has error message")
112
-
113
- # A6: RPC with missing method field
114
- r = post("/rpc", {"params": {}})
115
- check(r.status_code == 400, "Missing method field returns 400")
116
-
117
- # A7: RPC with empty body
118
- r = httpx.post(f"{BASE}/rpc", content=b"", headers={"Content-Type": "application/json"}, timeout=10)
119
- check(r.status_code == 400, "Empty RPC body returns 400")
120
-
121
- # A8: 404 for unknown route
122
- r = get("/nonexistent/route")
123
- check(r.status_code == 404, "Unknown route returns 404")
124
-
125
- # A9: CORS headers
126
- r = httpx.options(f"{BASE}/health", timeout=10)
127
- check(r.status_code == 204, "OPTIONS returns 204")
128
- check("access-control-allow-origin" in r.headers, "CORS headers present")
129
-
130
- # ══════════════════════════════════════════════════════════
131
- # Section B: SSE Events
132
- # ══════════════════════════════════════════════════════════
133
- section("B: SSE Events")
134
-
135
- sse_events: list[str] = []
136
- sse_connected = threading.Event()
137
-
138
- def sse_listener():
139
- try:
140
- with httpx.stream("GET", f"{BASE}/events", timeout=10) as stream:
141
- for line in stream.iter_lines():
142
- if line.startswith("data: "):
143
- data = line[6:]
144
- sse_events.append(data)
145
- parsed = json.loads(data)
146
- if parsed.get("type") == "connected":
147
- sse_connected.set()
148
- if len(sse_events) >= 3:
149
- break
150
- except (httpx.ReadTimeout, httpx.RemoteProtocolError):
151
- pass
152
-
153
- t = threading.Thread(target=sse_listener, daemon=True)
154
- t.start()
155
- sse_connected.wait(timeout=5)
156
- check(sse_connected.is_set(), "SSE connection established")
157
-
158
- # Trigger an event by reporting an error
159
- post("/errors", {
160
- "project": "test-sse",
161
- "errorOutput": "Error: SSE test trigger\n at test (/tmp/test.js:1:1)",
162
- })
163
- time.sleep(1)
164
- check(len(sse_events) >= 1, f"SSE received {len(sse_events)} event(s)")
165
-
166
- # ══════════════════════════════════════════════════════════
167
- # Section C: MCP HTTP/SSE
168
- # ══════════════════════════════════════════════════════════
169
- section("C: MCP HTTP/SSE")
170
-
171
- # C1: Root endpoint
172
- try:
173
- r = get("/", base=MCP_BASE)
174
- check(r.status_code == 200, "MCP root endpoint returns 200")
175
- mcp_info = r.json()
176
- check(mcp_info.get("name") == "brain", f"MCP name: {mcp_info.get('name')}")
177
- check(mcp_info.get("protocol") == "MCP", "MCP protocol field present")
178
- check("endpoints" in mcp_info, "MCP endpoints listed")
179
- except httpx.ConnectError:
180
- check(False, "MCP HTTP server reachable on port 7778")
181
- check(False, "MCP name check (skipped)")
182
- check(False, "MCP protocol check (skipped)")
183
- check(False, "MCP endpoints check (skipped)")
184
-
185
- # C2: SSE endpoint (just verify it starts streaming)
186
- try:
187
- with httpx.stream("GET", f"{MCP_BASE}/sse", timeout=5) as stream:
188
- first_chunk = None
189
- for line in stream.iter_lines():
190
- first_chunk = line
191
- break
192
- check(first_chunk is not None, f"MCP SSE stream started: {first_chunk[:50] if first_chunk else 'empty'}...")
193
- except (httpx.ReadTimeout, httpx.ConnectError):
194
- check(False, "MCP SSE connection (timeout or unreachable)")
195
-
196
- # C3: Messages endpoint without sessionId
197
- try:
198
- r = post("/messages", base=MCP_BASE)
199
- check(r.status_code == 400, "MCP /messages without sessionId returns 400")
200
- except httpx.ConnectError:
201
- check(False, "MCP /messages reachable")
202
-
203
- # ══════════════════════════════════════════════════════════
204
- # Section D: All REST Endpoints
205
- # ══════════════════════════════════════════════════════════
206
- section("D: All REST Endpoints (comprehensive)")
207
-
208
- # D1-D6: Error endpoints (basic shape verification)
209
- r = timed_get("/errors")
210
- check(r.status_code == 200, "GET /errors returns 200")
211
- errors = r.json().get("result", [])
212
- check(isinstance(errors, list), f"Errors list: {len(errors)} items")
213
-
214
- if errors:
215
- eid = errors[0]["id"] if isinstance(errors[0], dict) else errors[0]
216
- r = timed_get(f"/errors/{eid}")
217
- check(r.status_code == 200, f"GET /errors/{eid} returns 200")
218
-
219
- r = timed_get(f"/errors/{eid}/match")
220
- check(r.status_code == 200, f"GET /errors/{eid}/match returns 200")
221
-
222
- r = timed_get(f"/errors/{eid}/chain")
223
- check(r.status_code == 200, f"GET /errors/{eid}/chain returns 200")
224
-
225
- # D7-D10: Solution endpoints
226
- r = timed_get("/solutions")
227
- check(r.status_code == 200, "GET /solutions returns 200")
228
-
229
- r = timed_get("/solutions/efficiency")
230
- check(r.status_code == 200, "GET /solutions/efficiency returns 200")
231
-
232
- # D11: Projects
233
- r = timed_get("/projects")
234
- check(r.status_code == 200, "GET /projects returns 200")
235
-
236
- # D12-D16: Code endpoints
237
- r = timed_get("/code/modules")
238
- check(r.status_code == 200, "GET /code/modules returns 200")
239
- modules = r.json().get("result", [])
240
- if modules and isinstance(modules[0], dict):
241
- mid = modules[0]["id"]
242
- r = timed_get(f"/code/{mid}")
243
- check(r.status_code == 200, f"GET /code/{mid} returns 200")
244
-
245
- r = timed_post("/code/find", {"query": "utility"})
246
- check(r.status_code == 201, "POST /code/find returns 201")
247
-
248
- r = timed_post("/code/similarity", {"source": "function test() { return 42; }", "language": "typescript"})
249
- check(r.status_code == 201, "POST /code/similarity returns 201")
250
-
251
- # D17-D19: Prevention endpoints
252
- r = timed_post("/prevention/check", {"errorType": "Error", "message": "test"})
253
- check(r.status_code == 201, "POST /prevention/check returns 201")
254
-
255
- r = timed_post("/prevention/antipatterns", {"errorType": "Error", "message": "test"})
256
- check(r.status_code == 201, "POST /prevention/antipatterns returns 201")
257
-
258
- r = timed_post("/prevention/code", {"source": "let x = 1;", "filePath": "test.js"})
259
- check(r.status_code == 201, "POST /prevention/code returns 201")
260
-
261
- # D20-D23: Synapse endpoints
262
- r = timed_get("/synapses/stats")
263
- check(r.status_code == 200, "GET /synapses/stats returns 200")
264
- syn_stats = r.json().get("result", {})
265
- check(isinstance(syn_stats, dict), f"Synapse stats: {syn_stats.get('totalSynapses', '?')} synapses")
266
-
267
- if errors:
268
- eid = errors[0]["id"] if isinstance(errors[0], dict) else errors[0]
269
- r = timed_get(f"/synapses/context/{eid}")
270
- check(r.status_code == 200, f"GET /synapses/context/{eid} returns 200")
271
-
272
- r = timed_post("/synapses/related", {"nodeType": "error", "nodeId": 1})
273
- check(r.status_code == 201, "POST /synapses/related returns 201")
274
-
275
- r = timed_post("/synapses/path", {"fromType": "error", "fromId": 1, "toType": "solution", "toId": 1})
276
- check(r.status_code == 201, "POST /synapses/path returns 201")
277
-
278
- # D24-D27: Research/Insights endpoints
279
- r = timed_get("/research/insights")
280
- check(r.status_code == 200, "GET /research/insights returns 200")
281
-
282
- r = timed_get("/research/suggest", params={"context": "TypeError handling"})
283
- check(r.status_code == 200, "GET /research/suggest returns 200")
284
-
285
- r = timed_get("/research/trends")
286
- check(r.status_code == 200, "GET /research/trends returns 200")
287
-
288
- # D28-D29: Notifications
289
- r = timed_get("/notifications")
290
- check(r.status_code == 200, "GET /notifications returns 200")
291
-
292
- # D30-D34: Analytics endpoints
293
- r = timed_get("/analytics/summary")
294
- check(r.status_code == 200, "GET /analytics/summary returns 200")
295
- summary = r.json().get("result", {})
296
- check(isinstance(summary, dict), "Analytics summary is a dict")
297
-
298
- r = timed_get("/analytics/network")
299
- check(r.status_code == 200, "GET /analytics/network returns 200")
300
-
301
- r = timed_get("/analytics/health")
302
- check(r.status_code == 200, "GET /analytics/health returns 200")
303
-
304
- r = timed_get("/analytics/timeline")
305
- check(r.status_code == 200, "GET /analytics/timeline returns 200")
306
-
307
- if errors:
308
- eid = errors[0]["id"] if isinstance(errors[0], dict) else errors[0]
309
- r = timed_get(f"/analytics/explain/{eid}")
310
- check(r.status_code == 200, f"GET /analytics/explain/{eid} returns 200")
311
-
312
- # D35-D39: Git endpoints
313
- r = timed_get("/git/context")
314
- check(r.status_code == 200, "GET /git/context returns 200")
315
-
316
- r = timed_get("/git/diff")
317
- check(r.status_code == 200, "GET /git/diff returns 200")
318
-
319
- # D40: Learning
320
- r = timed_post("/learning/run")
321
- check(r.status_code == 201, "POST /learning/run returns 201")
322
-
323
- # ══════════════════════════════════════════════════════════
324
- # Section E: CLI Commands
325
- # ══════════════════════════════════════════════════════════
326
- section("E: CLI Commands")
327
-
328
- cli_commands = [
329
- (["brain", "status"], "brain status"),
330
- (["brain", "doctor"], "brain doctor"),
331
- (["brain", "query", "TypeError"], "brain query TypeError"),
332
- (["brain", "modules"], "brain modules"),
333
- (["brain", "insights"], "brain insights"),
334
- (["brain", "projects"], "brain projects"),
335
- (["brain", "network"], "brain network"),
336
- (["brain", "learn"], "brain learn"),
337
- ]
338
-
339
- for cmd, label in cli_commands:
340
- try:
341
- result = subprocess.run(
342
- cmd, capture_output=True, text=True, timeout=30,
343
- shell=(sys.platform == "win32"),
344
- )
345
- # Some commands may have non-zero exit codes if no data, but they shouldn't crash
346
- check(result.returncode == 0 or result.returncode is not None,
347
- f"CLI '{label}' ran (exit={result.returncode})")
348
- except FileNotFoundError:
349
- check(False, f"CLI '{label}' (brain not found in PATH)")
350
- except subprocess.TimeoutExpired:
351
- check(False, f"CLI '{label}' (timeout)")
352
-
353
- # ══════════════════════════════════════════════════════════
354
- # Section F: Git Integration
355
- # ══════════════════════════════════════════════════════════
356
- section("F: Git Integration")
357
-
358
- # F1: Git context
359
- r = get("/git/context")
360
- ctx = r.json().get("result", {})
361
- check(r.status_code == 200, "Git context returns 200")
362
- check(ctx.get("branch") is not None or ctx.get("branch") is None, f"Git branch: {ctx.get('branch')}")
363
-
364
- # F2: Link error to commit
365
- fake_hash = "abc1234567890def1234567890abcdef12345678"
366
- if errors:
367
- eid = errors[0]["id"] if isinstance(errors[0], dict) else errors[0]
368
- # Get project ID from the error
369
- err_detail = get(f"/errors/{eid}").json().get("result", {})
370
- pid = err_detail.get("project_id", 1)
371
- r = post("/git/link-error", {
372
- "errorId": eid,
373
- "projectId": pid,
374
- "commitHash": fake_hash,
375
- "relationship": "introduced_by",
376
- })
377
- check(r.status_code == 201, "Git link-error returns 201")
378
-
379
- # F3: Query commits by error
380
- r = get(f"/git/errors/{eid}/commits")
381
- check(r.status_code == 200, f"Git errorCommits returns 200")
382
-
383
- # F4: Query errors by commit
384
- r = get(f"/git/commits/{fake_hash}/errors")
385
- check(r.status_code == 200, "Git commitErrors returns 200")
386
-
387
- # F5: Git diff
388
- r = get("/git/diff")
389
- check(r.status_code == 200, "Git diff returns 200")
390
-
391
- # ══════════════════════════════════════════════════════════
392
- # Section G: Terminal Lifecycle
393
- # ══════════════════════════════════════════════════════════
394
- section("G: Terminal Lifecycle")
395
-
396
- term_uuid = str(uuid.uuid4())
397
-
398
- # G1: Register terminal
399
- r = post("/terminal/register", {
400
- "uuid": term_uuid,
401
- "pid": 12345,
402
- "shell": "bash",
403
- "cwd": "/tmp/test-project",
404
- })
405
- check(r.status_code == 201, "Terminal register returns 201")
406
- term_id = r.json().get("result")
407
- check(term_id is not None, f"Terminal registered (id={term_id})")
408
-
409
- # G2: Heartbeat
410
- r = post("/terminal/heartbeat", {"uuid": term_uuid})
411
- check(r.status_code == 201, "Terminal heartbeat returns 201")
412
-
413
- # G3: Disconnect
414
- r = post("/terminal/disconnect", {"uuid": term_uuid})
415
- check(r.status_code == 201, "Terminal disconnect returns 201")
416
-
417
- # ══════════════════════════════════════════════════════════
418
- # Section H: Notifications
419
- # ══════════════════════════════════════════════════════════
420
- section("H: Notifications")
421
-
422
- # H1: List notifications
423
- r = get("/notifications")
424
- check(r.status_code == 200, "Notifications list returns 200")
425
- notifs = r.json().get("result", [])
426
- check(isinstance(notifs, list), f"Notifications: {len(notifs)} items")
427
-
428
- # H2: Acknowledge a notification (if any exist)
429
- if notifs and isinstance(notifs[0], dict):
430
- nid = notifs[0].get("id")
431
- if nid:
432
- r = post(f"/notifications/{nid}/ack")
433
- check(r.status_code == 201, f"Notification {nid} acknowledged")
434
-
435
- # H3: Verify dismissal
436
- r = get("/notifications")
437
- new_notifs = r.json().get("result", [])
438
- dismissed = all(n.get("id") != nid for n in new_notifs if isinstance(n, dict))
439
- check(dismissed or len(new_notifs) <= len(notifs),
440
- f"Notification dismissed ({len(notifs)} → {len(new_notifs)})")
441
- else:
442
- check(True, "No notifications to acknowledge (OK)")
443
- check(True, "No notifications to verify dismissal (OK)")
444
-
445
- # ══════════════════════════════════════════════════════════
446
- # Section I: Performance Benchmarks
447
- # ══════════════════════════════════════════════════════════
448
- section("I: Performance Benchmarks")
449
-
450
- # Run a few extra timed calls for benchmark variety
451
- for _ in range(3):
452
- timed_get("/health")
453
- timed_get("/analytics/summary")
454
- timed_post("/rpc", {"method": "synapse.stats", "params": {}})
455
-
456
- # Print performance table
457
- if PERF:
458
- print(f"\n {'Endpoint':<45} {'Time (ms)':>10}")
459
- print(f" {'-' * 45} {'-' * 10}")
460
-
461
- # Group by endpoint and compute averages
462
- from collections import defaultdict
463
- groups: dict[str, list[float]] = defaultdict(list)
464
- for endpoint, ms in PERF:
465
- groups[endpoint].append(ms)
466
-
467
- for endpoint, times in sorted(groups.items()):
468
- avg = sum(times) / len(times)
469
- count = f" (×{len(times)})" if len(times) > 1 else ""
470
- color = "\033[32m" if avg < 50 else "\033[33m" if avg < 200 else "\033[31m"
471
- print(f" {endpoint:<45} {color}{avg:>8.1f}ms\033[0m{count}")
472
-
473
- all_times = [t for _, t in PERF]
474
- avg_all = sum(all_times) / len(all_times)
475
- max_time = max(all_times)
476
- p95 = sorted(all_times)[int(len(all_times) * 0.95)]
477
- print(f"\n Average: {avg_all:.1f}ms | P95: {p95:.1f}ms | Max: {max_time:.1f}ms")
478
-
479
- check(avg_all < 500, f"Average response time < 500ms ({avg_all:.1f}ms)")
480
- check(max_time < 5000, f"Max response time < 5s ({max_time:.1f}ms)")
481
-
482
- # ══════════════════════════════════════════════════════════
483
- # Section J: Error Handling
484
- # ══════════════════════════════════════════════════════════
485
- section("J: Error Handling")
486
-
487
- # J1: Invalid JSON body
488
- r = httpx.post(f"{BASE}/errors", content=b"not json", headers={"Content-Type": "application/json"}, timeout=10)
489
- check(r.status_code == 400, "Invalid JSON returns 400")
490
-
491
- # J2: Missing required fields
492
- r = post("/errors", {})
493
- # Should still work but with empty/default values, or return error
494
- check(r.status_code in (201, 400), f"Empty error body returns {r.status_code}")
495
-
496
- # J3: Non-existent error ID
497
- r = get("/errors/999999")
498
- check(r.status_code in (200, 400, 404), f"Non-existent error ID returns {r.status_code}")
499
-
500
- # J4: Non-existent code module
501
- r = get("/code/999999")
502
- check(r.status_code in (200, 400, 404), f"Non-existent module ID returns {r.status_code}")
503
-
504
- # J5: Invalid RPC method type
505
- r = post("/rpc", {"method": 12345, "params": {}})
506
- check(r.status_code in (200, 400), f"Invalid method type returns {r.status_code}")
507
-
508
- # ══════════════════════════════════════════════════════════
509
- # Summary
510
- # ══════════════════════════════════════════════════════════
511
- print("\n" + "=" * 60)
512
- total = PASS + FAIL
513
- print(f" Results: {PASS}/{total} passed, {FAIL} failed")
514
- if ERRORS:
515
- print(f"\n Failed tests:")
516
- for e in ERRORS:
517
- print(f" - {e}")
518
- print("=" * 60 + "\n")
519
-
520
- return 0 if FAIL == 0 else 1
521
-
522
-
523
- if __name__ == "__main__":
524
- try:
525
- sys.exit(main())
526
- except httpx.ConnectError:
527
- print("\n\033[31mERROR: Cannot connect to Brain daemon on port 7777.\033[0m")
528
- print("Run 'brain start' or 'brain doctor' first.\n")
529
- sys.exit(2)
530
- except Exception as e:
531
- print(f"\n\033[31mFATAL: {e}\033[0m\n")
532
- import traceback
533
- traceback.print_exc()
534
- sys.exit(2)
@@ -1,83 +0,0 @@
1
- export const retryModule = {
2
- name: 'retry',
3
- language: 'typescript',
4
- filePath: 'src/utils/retry.ts',
5
- source: `export async function retry<T>(
6
- fn: () => Promise<T>,
7
- maxAttempts: number = 3,
8
- delayMs: number = 1000,
9
- ): Promise<T> {
10
- let lastError: Error | undefined;
11
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
12
- try {
13
- return await fn();
14
- } catch (err) {
15
- lastError = err instanceof Error ? err : new Error(String(err));
16
- if (attempt < maxAttempts) {
17
- await new Promise(r => setTimeout(r, delayMs * attempt));
18
- }
19
- }
20
- }
21
- throw lastError;
22
- }`,
23
- description: 'Retry function with exponential backoff',
24
- };
25
-
26
- export const loggerModule = {
27
- name: 'logger',
28
- language: 'typescript',
29
- filePath: 'src/utils/logger.ts',
30
- source: `import winston from 'winston';
31
-
32
- export function createLogger(name: string, level: string = 'info') {
33
- return winston.createLogger({
34
- level,
35
- format: winston.format.combine(
36
- winston.format.timestamp(),
37
- winston.format.printf(({ timestamp, level, message }) =>
38
- \`[\${timestamp}] [\${level}] [\${name}] \${message}\`
39
- ),
40
- ),
41
- transports: [new winston.transports.Console()],
42
- });
43
- }`,
44
- description: 'Winston logger factory with timestamp formatting',
45
- };
46
-
47
- export const hashModule = {
48
- name: 'hash',
49
- language: 'python',
50
- filePath: 'utils/hash.py',
51
- source: `import hashlib
52
-
53
- def sha256(data: str) -> str:
54
- return hashlib.sha256(data.encode()).hexdigest()
55
-
56
- def md5(data: str) -> str:
57
- return hashlib.md5(data.encode()).hexdigest()
58
- `,
59
- description: 'Hash utility functions',
60
- };
61
-
62
- export const similarRetryModule = {
63
- name: 'retryWithBackoff',
64
- language: 'typescript',
65
- filePath: 'lib/retry-with-backoff.ts',
66
- source: `export async function retryWithBackoff<T>(
67
- operation: () => Promise<T>,
68
- retries: number = 3,
69
- baseDelay: number = 500,
70
- ): Promise<T> {
71
- let error: Error | undefined;
72
- for (let i = 0; i < retries; i++) {
73
- try {
74
- return await operation();
75
- } catch (e) {
76
- error = e instanceof Error ? e : new Error(String(e));
77
- await new Promise(r => setTimeout(r, baseDelay * Math.pow(2, i)));
78
- }
79
- }
80
- throw error;
81
- }`,
82
- description: 'Retry with exponential backoff',
83
- };
@@ -1,9 +0,0 @@
1
- export const goCompileError = `./main.go:15:2: undefined: fmt.Prinln
2
- ./main.go:20:5: cannot use "hello" (untyped string constant) as int value in assignment`;
3
-
4
- export const goPanicError = `goroutine 1 [running]:
5
- main.main()
6
- /app/main.go:12 +0x40
7
- panic: runtime error: index out of range [5] with length 3`;
8
-
9
- export const goTypeError = `./handler.go:25:15: cannot convert result (variable of type string) to type int`;
@@ -1,24 +0,0 @@
1
- export const nodeTypeError = `TypeError: Cannot read properties of undefined (reading 'map')
2
- at UserList (/app/src/components/UserList.tsx:15:23)
3
- at renderWithHooks (/app/node_modules/react-dom/cjs/react-dom.development.js:14985:18)
4
- at mountIndeterminateComponent (/app/node_modules/react-dom/cjs/react-dom.development.js:17811:13)`;
5
-
6
- export const nodeModuleNotFound = `Error: Cannot find module './config'
7
- Require stack:
8
- - /app/src/server.ts
9
- - /app/src/index.ts
10
- at Function.Module._resolveFilename (node:internal/modules/cjs/loader:995:15)
11
- at Function.Module._load (node:internal/modules/cjs/loader:841:27)`;
12
-
13
- export const nodeReferenceError = `ReferenceError: process is not defined
14
- at Object.<anonymous> (/app/src/utils/env.ts:3:18)
15
- at Module._compile (node:internal/modules/cjs/loader:1234:14)`;
16
-
17
- export const nodeSyntaxError = `SyntaxError: Unexpected token '}'
18
- at wrapSafe (node:internal/modules/cjs/loader:1278:20)
19
- at Module._compile (node:internal/modules/cjs/loader:1320:27)
20
- at /app/src/parser.ts:42:5`;
21
-
22
- export const nodeEnoent = `Error: ENOENT: no such file or directory, open '/app/data/config.json'
23
- at Object.openSync (node:fs:600:3)
24
- at Object.readFileSync (node:fs:468:35)`;
@@ -1,21 +0,0 @@
1
- export const pythonTraceback = `Traceback (most recent call last):
2
- File "/app/main.py", line 42, in <module>
3
- result = process_data(data)
4
- File "/app/processor.py", line 15, in process_data
5
- return data["key"]["nested"]
6
- KeyError: 'nested'`;
7
-
8
- export const pythonImportError = `Traceback (most recent call last):
9
- File "/app/server.py", line 1, in <module>
10
- from flask import Flask
11
- ModuleNotFoundError: No module named 'flask'`;
12
-
13
- export const pythonTypeError = `Traceback (most recent call last):
14
- File "/app/calc.py", line 10, in compute
15
- return x + y
16
- TypeError: unsupported operand type(s) for +: 'int' and 'str'`;
17
-
18
- export const pythonValueError = `Traceback (most recent call last):
19
- File "/app/parser.py", line 25, in parse_int
20
- return int(value)
21
- ValueError: invalid literal for int() with base 10: 'abc'`;