@timmeck/brain 1.8.0 → 1.8.2
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.
- package/BRAIN_PLAN.md +3324 -3324
- package/LICENSE +21 -21
- package/dist/api/server.d.ts +4 -0
- package/dist/api/server.js +73 -0
- package/dist/api/server.js.map +1 -1
- package/dist/brain.js +2 -1
- package/dist/brain.js.map +1 -1
- package/dist/cli/commands/dashboard.js +606 -572
- package/dist/cli/commands/dashboard.js.map +1 -1
- package/dist/dashboard/server.js +25 -25
- package/dist/db/migrations/001_core_schema.js +115 -115
- package/dist/db/migrations/002_learning_schema.js +33 -33
- package/dist/db/migrations/003_code_schema.js +48 -48
- package/dist/db/migrations/004_synapses_schema.js +52 -52
- package/dist/db/migrations/005_fts_indexes.js +73 -73
- package/dist/db/migrations/007_feedback.js +8 -8
- package/dist/db/migrations/008_git_integration.js +33 -33
- package/dist/db/migrations/009_embeddings.js +3 -3
- package/dist/db/repositories/antipattern.repository.js +3 -3
- package/dist/db/repositories/code-module.repository.js +32 -32
- package/dist/db/repositories/notification.repository.js +3 -3
- package/dist/db/repositories/project.repository.js +21 -21
- package/dist/db/repositories/rule.repository.js +24 -24
- package/dist/db/repositories/solution.repository.js +50 -50
- package/dist/db/repositories/synapse.repository.js +18 -18
- package/dist/db/repositories/terminal.repository.js +24 -24
- package/dist/embeddings/engine.d.ts +2 -2
- package/dist/embeddings/engine.js +17 -4
- package/dist/embeddings/engine.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/ipc/server.d.ts +8 -0
- package/dist/ipc/server.js +67 -1
- package/dist/ipc/server.js.map +1 -1
- package/dist/matching/error-matcher.js +5 -5
- package/dist/matching/fingerprint.js +6 -1
- package/dist/matching/fingerprint.js.map +1 -1
- package/dist/mcp/http-server.js +8 -2
- package/dist/mcp/http-server.js.map +1 -1
- package/dist/services/code.service.d.ts +3 -0
- package/dist/services/code.service.js +33 -4
- package/dist/services/code.service.js.map +1 -1
- package/dist/services/error.service.js +4 -3
- package/dist/services/error.service.js.map +1 -1
- package/dist/services/git.service.js +14 -14
- package/package.json +49 -49
- package/src/api/server.ts +395 -321
- package/src/brain.ts +266 -265
- package/src/cli/colors.ts +116 -116
- package/src/cli/commands/config.ts +169 -169
- package/src/cli/commands/dashboard.ts +755 -720
- package/src/cli/commands/doctor.ts +118 -118
- package/src/cli/commands/explain.ts +83 -83
- package/src/cli/commands/export.ts +31 -31
- package/src/cli/commands/import.ts +199 -199
- package/src/cli/commands/insights.ts +65 -65
- package/src/cli/commands/learn.ts +24 -24
- package/src/cli/commands/modules.ts +53 -53
- package/src/cli/commands/network.ts +67 -67
- package/src/cli/commands/projects.ts +42 -42
- package/src/cli/commands/query.ts +120 -120
- package/src/cli/commands/start.ts +62 -62
- package/src/cli/commands/status.ts +75 -75
- package/src/cli/commands/stop.ts +34 -34
- package/src/cli/ipc-helper.ts +22 -22
- package/src/cli/update-check.ts +63 -63
- package/src/code/fingerprint.ts +87 -87
- package/src/code/parsers/generic.ts +29 -29
- package/src/code/parsers/python.ts +54 -54
- package/src/code/parsers/typescript.ts +65 -65
- package/src/code/registry.ts +60 -60
- package/src/dashboard/server.ts +142 -142
- package/src/db/connection.ts +22 -22
- package/src/db/migrations/001_core_schema.ts +120 -120
- package/src/db/migrations/002_learning_schema.ts +38 -38
- package/src/db/migrations/003_code_schema.ts +53 -53
- package/src/db/migrations/004_synapses_schema.ts +57 -57
- package/src/db/migrations/005_fts_indexes.ts +78 -78
- package/src/db/migrations/006_synapses_phase3.ts +17 -17
- package/src/db/migrations/007_feedback.ts +13 -13
- package/src/db/migrations/008_git_integration.ts +38 -38
- package/src/db/migrations/009_embeddings.ts +8 -8
- package/src/db/repositories/antipattern.repository.ts +66 -66
- package/src/db/repositories/code-module.repository.ts +142 -142
- package/src/db/repositories/notification.repository.ts +66 -66
- package/src/db/repositories/project.repository.ts +93 -93
- package/src/db/repositories/rule.repository.ts +108 -108
- package/src/db/repositories/solution.repository.ts +154 -154
- package/src/db/repositories/synapse.repository.ts +153 -153
- package/src/db/repositories/terminal.repository.ts +101 -101
- package/src/embeddings/engine.ts +238 -217
- package/src/index.ts +63 -63
- package/src/ipc/client.ts +118 -118
- package/src/ipc/protocol.ts +35 -35
- package/src/ipc/router.ts +133 -133
- package/src/ipc/server.ts +176 -110
- package/src/learning/decay.ts +46 -46
- package/src/learning/pattern-extractor.ts +90 -90
- package/src/learning/rule-generator.ts +74 -74
- package/src/matching/error-matcher.ts +5 -5
- package/src/matching/fingerprint.ts +34 -29
- package/src/matching/similarity.ts +61 -61
- package/src/matching/tfidf.ts +74 -74
- package/src/matching/tokenizer.ts +41 -41
- package/src/mcp/auto-detect.ts +93 -93
- package/src/mcp/http-server.ts +140 -137
- package/src/mcp/server.ts +73 -73
- package/src/parsing/error-parser.ts +28 -28
- package/src/parsing/parsers/compiler.ts +93 -93
- package/src/parsing/parsers/generic.ts +28 -28
- package/src/parsing/parsers/go.ts +97 -97
- package/src/parsing/parsers/node.ts +69 -69
- package/src/parsing/parsers/python.ts +62 -62
- package/src/parsing/parsers/rust.ts +50 -50
- package/src/parsing/parsers/shell.ts +42 -42
- package/src/parsing/types.ts +47 -47
- package/src/research/gap-analyzer.ts +135 -135
- package/src/research/insight-generator.ts +123 -123
- package/src/research/research-engine.ts +116 -116
- package/src/research/synergy-detector.ts +126 -126
- package/src/research/template-extractor.ts +130 -130
- package/src/research/trend-analyzer.ts +127 -127
- package/src/services/code.service.ts +271 -238
- package/src/services/error.service.ts +4 -3
- package/src/services/git.service.ts +132 -132
- package/src/services/notification.service.ts +41 -41
- package/src/services/synapse.service.ts +59 -59
- package/src/services/terminal.service.ts +81 -81
- package/src/synapses/activation.ts +80 -80
- package/src/synapses/decay.ts +38 -38
- package/src/synapses/hebbian.ts +69 -69
- package/src/synapses/pathfinder.ts +81 -81
- package/src/synapses/synapse-manager.ts +109 -109
- package/src/types/code.types.ts +52 -52
- package/src/types/error.types.ts +67 -67
- package/src/types/ipc.types.ts +8 -8
- package/src/types/mcp.types.ts +53 -53
- package/src/types/research.types.ts +28 -28
- package/src/types/solution.types.ts +30 -30
- package/src/utils/events.ts +45 -45
- package/src/utils/hash.ts +5 -5
- package/src/utils/logger.ts +48 -48
- package/src/utils/paths.ts +19 -19
- package/tests/e2e/test_code_intelligence.py +1015 -0
- package/tests/e2e/test_error_memory.py +451 -0
- package/tests/e2e/test_full_integration.py +534 -0
- package/tests/fixtures/code-modules/modules.ts +83 -83
- package/tests/fixtures/errors/go.ts +9 -9
- package/tests/fixtures/errors/node.ts +24 -24
- package/tests/fixtures/errors/python.ts +21 -21
- package/tests/fixtures/errors/rust.ts +25 -25
- package/tests/fixtures/errors/shell.ts +15 -15
- package/tests/fixtures/solutions/solutions.ts +27 -27
- package/tests/helpers/setup-db.ts +52 -52
- package/tests/integration/code-flow.test.ts +86 -86
- package/tests/integration/error-flow.test.ts +83 -83
- package/tests/integration/ipc-flow.test.ts +166 -166
- package/tests/integration/learning-cycle.test.ts +82 -82
- package/tests/integration/synapse-flow.test.ts +117 -117
- package/tests/unit/code/analyzer.test.ts +58 -58
- package/tests/unit/code/fingerprint.test.ts +51 -51
- package/tests/unit/code/scorer.test.ts +55 -55
- package/tests/unit/learning/confidence-scorer.test.ts +60 -60
- package/tests/unit/learning/decay.test.ts +45 -45
- package/tests/unit/learning/pattern-extractor.test.ts +50 -50
- package/tests/unit/matching/error-matcher.test.ts +69 -69
- package/tests/unit/matching/fingerprint.test.ts +47 -47
- package/tests/unit/matching/similarity.test.ts +65 -65
- package/tests/unit/matching/tfidf.test.ts +71 -71
- package/tests/unit/matching/tokenizer.test.ts +83 -83
- package/tests/unit/parsing/parsers.test.ts +113 -113
- package/tests/unit/research/gap-analyzer.test.ts +45 -45
- package/tests/unit/research/trend-analyzer.test.ts +45 -45
- package/tests/unit/synapses/activation.test.ts +80 -80
- package/tests/unit/synapses/decay.test.ts +27 -27
- package/tests/unit/synapses/hebbian.test.ts +96 -96
- package/tests/unit/synapses/pathfinder.test.ts +72 -72
- package/tsconfig.json +18 -18
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Brain v1.8.1 — Error Memory Complete Flow Test
|
|
4
|
+
Tests the full error lifecycle: report → match → solve → learn → prevent
|
|
5
|
+
~50 assertions covering every error-related endpoint.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
BASE = "http://localhost:7777/api/v1"
|
|
13
|
+
PASS = 0
|
|
14
|
+
FAIL = 0
|
|
15
|
+
ERRORS: list[str] = []
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def check(condition: bool, label: str) -> bool:
|
|
19
|
+
global PASS, FAIL
|
|
20
|
+
if condition:
|
|
21
|
+
PASS += 1
|
|
22
|
+
print(f" \033[32mPASS\033[0m {label}")
|
|
23
|
+
else:
|
|
24
|
+
FAIL += 1
|
|
25
|
+
ERRORS.append(label)
|
|
26
|
+
print(f" \033[31mFAIL\033[0m {label}")
|
|
27
|
+
return condition
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def post(path: str, json: dict | list | None = None) -> httpx.Response:
|
|
31
|
+
return httpx.post(f"{BASE}{path}", json=json or {}, timeout=15)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get(path: str, params: dict | None = None) -> httpx.Response:
|
|
35
|
+
return httpx.get(f"{BASE}{path}", params=params, timeout=15)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ──────────────────────────────────────────────────────────────
|
|
39
|
+
# Test Data: 12 realistic errors across 2 projects, 3 languages
|
|
40
|
+
# ──────────────────────────────────────────────────────────────
|
|
41
|
+
TYPESCRIPT_ERRORS = [
|
|
42
|
+
{
|
|
43
|
+
"project": "test-frontend",
|
|
44
|
+
"errorOutput": """TypeError: Cannot read properties of undefined (reading 'map')
|
|
45
|
+
at UserList (/app/src/components/UserList.tsx:24:18)
|
|
46
|
+
at renderWithHooks (/app/node_modules/react-dom/cjs/react-dom.development.js:14985:18)
|
|
47
|
+
at mountIndeterminateComponent (/app/node_modules/react-dom/cjs/react-dom.development.js:17811:13)""",
|
|
48
|
+
"filePath": "/app/src/components/UserList.tsx",
|
|
49
|
+
"command": "npm run dev",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"project": "test-frontend",
|
|
53
|
+
"errorOutput": """SyntaxError: Unexpected token '<' (at index.html:1:1)
|
|
54
|
+
at Object.compileFunction (node:vm:360:18)
|
|
55
|
+
at wrapSafe (node:internal/modules/cjs/loader:1094:15)
|
|
56
|
+
at Module._compile (node:internal/modules/cjs/loader:1129:27)""",
|
|
57
|
+
"filePath": "/app/public/index.html",
|
|
58
|
+
"command": "npm run build",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"project": "test-frontend",
|
|
62
|
+
"errorOutput": """RangeError: Maximum call stack size exceeded
|
|
63
|
+
at deepClone (/app/src/utils/clone.ts:8:12)
|
|
64
|
+
at deepClone (/app/src/utils/clone.ts:15:16)
|
|
65
|
+
at deepClone (/app/src/utils/clone.ts:15:16)
|
|
66
|
+
at deepClone (/app/src/utils/clone.ts:15:16)""",
|
|
67
|
+
"filePath": "/app/src/utils/clone.ts",
|
|
68
|
+
"command": "npm test",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"project": "test-frontend",
|
|
72
|
+
"errorOutput": """Error: ENOENT: no such file or directory, open '/app/config/settings.json'
|
|
73
|
+
at Object.openSync (node:fs:603:3)
|
|
74
|
+
at readFileSync (node:fs:471:35)
|
|
75
|
+
at loadConfig (/app/src/config/loader.ts:12:22)""",
|
|
76
|
+
"filePath": "/app/src/config/loader.ts",
|
|
77
|
+
"command": "npm start",
|
|
78
|
+
},
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
PYTHON_ERRORS = [
|
|
82
|
+
{
|
|
83
|
+
"project": "test-backend",
|
|
84
|
+
"errorOutput": """Traceback (most recent call last):
|
|
85
|
+
File "/app/src/api/routes.py", line 45, in get_user
|
|
86
|
+
user = db.users.find_one({"_id": ObjectId(user_id)})
|
|
87
|
+
File "/app/venv/lib/python3.11/site-packages/pymongo/collection.py", line 1382, in find_one
|
|
88
|
+
return next(cursor, None)
|
|
89
|
+
bson.errors.InvalidId: '123abc' is not a valid ObjectId, it must be a 12-byte input or a 24-character hex string""",
|
|
90
|
+
"filePath": "/app/src/api/routes.py",
|
|
91
|
+
"command": "python -m pytest",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"project": "test-backend",
|
|
95
|
+
"errorOutput": """Traceback (most recent call last):
|
|
96
|
+
File "/app/src/services/auth.py", line 67, in verify_token
|
|
97
|
+
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
|
|
98
|
+
File "/app/venv/lib/python3.11/site-packages/jwt/api_jwt.py", line 210, in decode
|
|
99
|
+
decoded = api_jws.decode(jwt_value, key, algorithms)
|
|
100
|
+
jwt.exceptions.ExpiredSignatureError: Signature has expired""",
|
|
101
|
+
"filePath": "/app/src/services/auth.py",
|
|
102
|
+
"command": "python manage.py runserver",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"project": "test-backend",
|
|
106
|
+
"errorOutput": """Traceback (most recent call last):
|
|
107
|
+
File "/app/src/tasks/worker.py", line 23, in process_job
|
|
108
|
+
result = heavy_computation(data)
|
|
109
|
+
File "/app/src/tasks/compute.py", line 89, in heavy_computation
|
|
110
|
+
return np.dot(matrix_a, matrix_b)
|
|
111
|
+
MemoryError: Unable to allocate 2.00 GiB for an array with shape (16384, 16384) and data type float64""",
|
|
112
|
+
"filePath": "/app/src/tasks/compute.py",
|
|
113
|
+
"command": "celery -A app worker",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"project": "test-backend",
|
|
117
|
+
"errorOutput": """Traceback (most recent call last):
|
|
118
|
+
File "/app/src/db/connection.py", line 31, in connect
|
|
119
|
+
self.conn = psycopg2.connect(dsn=self.dsn, connect_timeout=5)
|
|
120
|
+
File "/app/venv/lib/python3.11/site-packages/psycopg2/__init__.py", line 122, in connect
|
|
121
|
+
conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
|
|
122
|
+
psycopg2.OperationalError: could not connect to server: Connection refused
|
|
123
|
+
\tIs the server running on host "localhost" (127.0.0.1) and accepting TCP/IP connections on port 5432?""",
|
|
124
|
+
"filePath": "/app/src/db/connection.py",
|
|
125
|
+
"command": "python manage.py migrate",
|
|
126
|
+
},
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
RUST_ERRORS = [
|
|
130
|
+
{
|
|
131
|
+
"project": "test-backend",
|
|
132
|
+
"errorOutput": """thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/config/loader.rs:42:10
|
|
133
|
+
stack backtrace:
|
|
134
|
+
0: rust_begin_unwind at /rustc/a178d0322ce20e33eac124758e837cbd80a6f633/library/std/src/panicking.rs:652:5
|
|
135
|
+
1: core::panicking::panic_fmt at /rustc/a178d0322ce20e33eac124758e837cbd80a6f633/library/core/src/panicking.rs:72:14
|
|
136
|
+
2: core::result::unwrap_failed at /rustc/a178d0322ce20e33eac124758e837cbd80a6f633/library/core/src/result.rs:1654:5
|
|
137
|
+
3: myapp::config::loader::load_config at ./src/config/loader.rs:42:10""",
|
|
138
|
+
"filePath": "src/config/loader.rs",
|
|
139
|
+
"command": "cargo run",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"project": "test-backend",
|
|
143
|
+
"errorOutput": """thread 'tokio-runtime-worker' panicked at 'index out of bounds: the len is 3 but the index is 5', src/handlers/api.rs:78:22
|
|
144
|
+
stack backtrace:
|
|
145
|
+
0: rust_begin_unwind
|
|
146
|
+
1: core::panicking::panic_fmt
|
|
147
|
+
2: core::panicking::panic_bounds_check
|
|
148
|
+
3: myapp::handlers::api::process_items at ./src/handlers/api.rs:78:22
|
|
149
|
+
4: myapp::handlers::api::handle_request at ./src/handlers/api.rs:45:9""",
|
|
150
|
+
"filePath": "src/handlers/api.rs",
|
|
151
|
+
"command": "cargo test",
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"project": "test-frontend",
|
|
155
|
+
"errorOutput": """TypeError: Cannot read properties of null (reading 'addEventListener')
|
|
156
|
+
at initDropdown (/app/src/components/Dropdown.tsx:18:8)
|
|
157
|
+
at mountComponent (/app/node_modules/react-dom/cjs/react-dom.development.js:14985:18)
|
|
158
|
+
at commitLayoutEffects (/app/node_modules/react-dom/cjs/react-dom.development.js:23426:9)""",
|
|
159
|
+
"filePath": "/app/src/components/Dropdown.tsx",
|
|
160
|
+
"command": "npm run dev",
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"project": "test-frontend",
|
|
164
|
+
"errorOutput": """ReferenceError: process is not defined
|
|
165
|
+
at getEnvVar (/app/src/utils/env.ts:3:10)
|
|
166
|
+
at loadConfig (/app/src/config/index.ts:8:22)
|
|
167
|
+
at Object.<anonymous> (/app/src/index.ts:4:1)""",
|
|
168
|
+
"filePath": "/app/src/utils/env.ts",
|
|
169
|
+
"command": "npm run build",
|
|
170
|
+
},
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
ALL_ERRORS = TYPESCRIPT_ERRORS + PYTHON_ERRORS + RUST_ERRORS
|
|
174
|
+
|
|
175
|
+
# ──────────────────────────────────────────────────────────────
|
|
176
|
+
# Solutions to report
|
|
177
|
+
# ──────────────────────────────────────────────────────────────
|
|
178
|
+
SOLUTIONS = [
|
|
179
|
+
{
|
|
180
|
+
"description": "Add optional chaining before .map() and provide fallback empty array",
|
|
181
|
+
"commands": None,
|
|
182
|
+
"codeChange": "const items = data?.users?.map(u => u.name) ?? [];",
|
|
183
|
+
"source": "manual",
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"description": "Validate ObjectId format before querying database",
|
|
187
|
+
"commands": "pip install bson",
|
|
188
|
+
"codeChange": "if not ObjectId.is_valid(user_id): raise HTTPException(400, 'Invalid ID')",
|
|
189
|
+
"source": "manual",
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"description": "Implement token refresh flow with sliding window expiration",
|
|
193
|
+
"commands": None,
|
|
194
|
+
"codeChange": "token = jwt.encode({...payload, 'exp': datetime.utcnow() + timedelta(hours=24)}, SECRET_KEY)",
|
|
195
|
+
"source": "manual",
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"description": "Use chunk processing to avoid memory allocation failures",
|
|
199
|
+
"commands": None,
|
|
200
|
+
"codeChange": "result = np.zeros(shape); for i in range(0, n, chunk_size): result[i:i+chunk_size] = np.dot(a[i:i+chunk_size], b)",
|
|
201
|
+
"source": "auto",
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"description": "Add connection retry with exponential backoff for database connections",
|
|
205
|
+
"commands": "pip install tenacity",
|
|
206
|
+
"codeChange": "@retry(wait=wait_exponential(min=1, max=30), stop=stop_after_attempt(5))\ndef connect(self): ...",
|
|
207
|
+
"source": "manual",
|
|
208
|
+
},
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def main() -> int:
|
|
213
|
+
print("\n" + "=" * 60)
|
|
214
|
+
print(" BRAIN E2E TEST: Error Memory Complete Flow")
|
|
215
|
+
print("=" * 60)
|
|
216
|
+
|
|
217
|
+
# Store IDs for later use
|
|
218
|
+
error_ids: list[int] = []
|
|
219
|
+
solution_ids: list[int] = []
|
|
220
|
+
|
|
221
|
+
# ── 1. Report 12 realistic errors ──────────────────────────
|
|
222
|
+
print("\n[1] Reporting 12 errors across 2 projects...")
|
|
223
|
+
for i, err in enumerate(ALL_ERRORS):
|
|
224
|
+
r = post("/errors", err)
|
|
225
|
+
ok = r.status_code == 201
|
|
226
|
+
data = r.json().get("result", {})
|
|
227
|
+
eid = data.get("errorId")
|
|
228
|
+
is_new = data.get("isNew")
|
|
229
|
+
check(ok and eid is not None, f"Error #{i+1} reported (id={eid})")
|
|
230
|
+
if eid:
|
|
231
|
+
error_ids.append(eid)
|
|
232
|
+
|
|
233
|
+
check(len(error_ids) == 12, f"All 12 errors created ({len(error_ids)} IDs)")
|
|
234
|
+
|
|
235
|
+
# ── 2. Duplicate detection ─────────────────────────────────
|
|
236
|
+
print("\n[2] Testing duplicate detection...")
|
|
237
|
+
r = post("/errors", ALL_ERRORS[0])
|
|
238
|
+
data = r.json().get("result", {})
|
|
239
|
+
dup_is_new = data.get("isNew")
|
|
240
|
+
check(dup_is_new is False, f"Duplicate detected (isNew={dup_is_new})")
|
|
241
|
+
check(data.get("errorId") == error_ids[0], "Duplicate returns same errorId")
|
|
242
|
+
|
|
243
|
+
# ── 3. Similar error matching ──────────────────────────────
|
|
244
|
+
print("\n[3] Testing similar error matching...")
|
|
245
|
+
# Report a near-duplicate (similar TypeError but slightly different)
|
|
246
|
+
near_dup = {
|
|
247
|
+
"project": "test-frontend",
|
|
248
|
+
"errorOutput": """TypeError: Cannot read properties of undefined (reading 'forEach')
|
|
249
|
+
at UserGrid (/app/src/components/UserGrid.tsx:31:12)
|
|
250
|
+
at renderWithHooks (/app/node_modules/react-dom/cjs/react-dom.development.js:14985:18)""",
|
|
251
|
+
"filePath": "/app/src/components/UserGrid.tsx",
|
|
252
|
+
"command": "npm run dev",
|
|
253
|
+
}
|
|
254
|
+
r = post("/errors", near_dup)
|
|
255
|
+
near_dup_id = r.json().get("result", {}).get("errorId")
|
|
256
|
+
matches_inline = r.json().get("result", {}).get("matches", [])
|
|
257
|
+
check(near_dup_id is not None, f"Near-duplicate reported (id={near_dup_id})")
|
|
258
|
+
|
|
259
|
+
# Explicitly call match endpoint
|
|
260
|
+
if error_ids:
|
|
261
|
+
r = get(f"/errors/{error_ids[0]}/match")
|
|
262
|
+
match_data = r.json().get("result", [])
|
|
263
|
+
check(r.status_code == 200, "Match endpoint returns 200")
|
|
264
|
+
check(isinstance(match_data, list), f"Match returns list ({len(match_data)} matches)")
|
|
265
|
+
|
|
266
|
+
# ── 4. Report 5 solutions linked to errors ─────────────────
|
|
267
|
+
print("\n[4] Reporting 5 solutions...")
|
|
268
|
+
for i, sol in enumerate(SOLUTIONS):
|
|
269
|
+
payload = {**sol, "errorId": error_ids[i] if i < len(error_ids) else error_ids[0]}
|
|
270
|
+
r = post("/solutions", payload)
|
|
271
|
+
sid = r.json().get("result")
|
|
272
|
+
check(r.status_code == 201 and sid is not None, f"Solution #{i+1} reported (id={sid})")
|
|
273
|
+
if sid is not None:
|
|
274
|
+
solution_ids.append(sid)
|
|
275
|
+
|
|
276
|
+
check(len(solution_ids) >= 4, f"At least 4 solutions created ({len(solution_ids)})")
|
|
277
|
+
|
|
278
|
+
# ── 5. Query solutions for an error ────────────────────────
|
|
279
|
+
print("\n[5] Querying solutions for error...")
|
|
280
|
+
if error_ids:
|
|
281
|
+
r = get("/solutions", params={"errorId": str(error_ids[0])})
|
|
282
|
+
check(r.status_code == 200, "Solution query returns 200")
|
|
283
|
+
sols = r.json().get("result", [])
|
|
284
|
+
check(isinstance(sols, list) and len(sols) >= 1, f"Found {len(sols)} solution(s) for error")
|
|
285
|
+
|
|
286
|
+
# ── 6. Rate solution outcomes ──────────────────────────────
|
|
287
|
+
print("\n[6] Rating solution outcomes...")
|
|
288
|
+
ratings = [
|
|
289
|
+
(0, 0, True), # sol 0 for error 0: success
|
|
290
|
+
(1, 1, True), # sol 1 for error 1 (mapped to error_ids[4]): success
|
|
291
|
+
(2, 2, True), # sol 2 for error 2 (mapped to error_ids[5]): success
|
|
292
|
+
(3, 3, False), # sol 3 for error 3 (mapped to error_ids[6]): failure
|
|
293
|
+
]
|
|
294
|
+
for sol_idx, err_idx, success in ratings:
|
|
295
|
+
if sol_idx < len(solution_ids) and err_idx < len(error_ids):
|
|
296
|
+
payload = {
|
|
297
|
+
"errorId": error_ids[err_idx],
|
|
298
|
+
"solutionId": solution_ids[sol_idx],
|
|
299
|
+
"success": success,
|
|
300
|
+
"output": "Applied successfully" if success else "Still failing",
|
|
301
|
+
"durationMs": 1200 if success else 5000,
|
|
302
|
+
}
|
|
303
|
+
r = post("/solutions/rate", payload)
|
|
304
|
+
label = "success" if success else "failure"
|
|
305
|
+
check(r.status_code == 201, f"Rated solution #{sol_idx+1} as {label}")
|
|
306
|
+
|
|
307
|
+
# ── 7. Solution efficiency ─────────────────────────────────
|
|
308
|
+
print("\n[7] Checking solution efficiency...")
|
|
309
|
+
r = get("/solutions/efficiency")
|
|
310
|
+
check(r.status_code == 200, "Efficiency endpoint returns 200")
|
|
311
|
+
eff = r.json().get("result")
|
|
312
|
+
check(eff is not None, f"Efficiency data returned: {type(eff)}")
|
|
313
|
+
|
|
314
|
+
# ── 8. Error chains ────────────────────────────────────────
|
|
315
|
+
print("\n[8] Testing error chains...")
|
|
316
|
+
if error_ids:
|
|
317
|
+
r = get(f"/errors/{error_ids[0]}/chain")
|
|
318
|
+
check(r.status_code == 200, "Chain endpoint returns 200")
|
|
319
|
+
chain = r.json().get("result")
|
|
320
|
+
check(chain is not None, f"Chain data returned: {type(chain)}")
|
|
321
|
+
|
|
322
|
+
# ── 9. Cross-project matching ──────────────────────────────
|
|
323
|
+
print("\n[9] Testing cross-project matching...")
|
|
324
|
+
# Error 3 (ENOENT frontend) and Error 8 (Rust NotFound) are conceptually similar
|
|
325
|
+
if len(error_ids) >= 9:
|
|
326
|
+
r = get(f"/errors/{error_ids[3]}/match")
|
|
327
|
+
cross_matches = r.json().get("result", [])
|
|
328
|
+
check(r.status_code == 200, "Cross-project match returns 200")
|
|
329
|
+
check(isinstance(cross_matches, list), f"Cross-project matches: {len(cross_matches)}")
|
|
330
|
+
|
|
331
|
+
# ── 10. Query/filter errors ────────────────────────────────
|
|
332
|
+
print("\n[10] Querying and filtering errors...")
|
|
333
|
+
r = get("/errors", params={"search": "TypeError"})
|
|
334
|
+
check(r.status_code == 200, "Error query by text returns 200")
|
|
335
|
+
results = r.json().get("result", [])
|
|
336
|
+
check(isinstance(results, list) and len(results) >= 1, f"Found {len(results)} TypeErrors")
|
|
337
|
+
|
|
338
|
+
r = get("/errors", params={"search": "database"})
|
|
339
|
+
check(r.status_code == 200, "Error query 'database' returns 200")
|
|
340
|
+
|
|
341
|
+
# Get single error
|
|
342
|
+
if error_ids:
|
|
343
|
+
r = get(f"/errors/{error_ids[0]}")
|
|
344
|
+
check(r.status_code == 200, "Get single error returns 200")
|
|
345
|
+
err_detail = r.json().get("result", {})
|
|
346
|
+
check(err_detail.get("id") == error_ids[0], "Error detail has correct ID")
|
|
347
|
+
|
|
348
|
+
# ── 11. Resolve an error with a solution ───────────────────
|
|
349
|
+
print("\n[11] Resolving an error...")
|
|
350
|
+
if error_ids and solution_ids:
|
|
351
|
+
r = post(f"/errors/{error_ids[0]}/resolve", {"solutionId": solution_ids[0]})
|
|
352
|
+
check(r.status_code == 201, "Resolve endpoint returns 201")
|
|
353
|
+
|
|
354
|
+
# Verify resolved
|
|
355
|
+
r = get(f"/errors/{error_ids[0]}")
|
|
356
|
+
resolved = r.json().get("result", {}).get("resolved")
|
|
357
|
+
check(resolved == 1 or resolved is True, f"Error marked as resolved (resolved={resolved})")
|
|
358
|
+
|
|
359
|
+
# ── 12. Analytics explain ──────────────────────────────────
|
|
360
|
+
print("\n[12] Testing analytics explain...")
|
|
361
|
+
if error_ids:
|
|
362
|
+
r = get(f"/analytics/explain/{error_ids[0]}")
|
|
363
|
+
check(r.status_code == 200, "Explain endpoint returns 200")
|
|
364
|
+
explanation = r.json().get("result")
|
|
365
|
+
check(explanation is not None, "Explanation data returned")
|
|
366
|
+
if isinstance(explanation, dict):
|
|
367
|
+
check("error" in explanation or "solutions" in explanation or "context" in explanation,
|
|
368
|
+
"Explanation has expected structure")
|
|
369
|
+
|
|
370
|
+
# ── 13. Trigger learning cycle ─────────────────────────────
|
|
371
|
+
print("\n[13] Triggering learning cycle...")
|
|
372
|
+
r = post("/learning/run")
|
|
373
|
+
check(r.status_code == 201, "Learning run endpoint returns 201")
|
|
374
|
+
learning_result = r.json().get("result")
|
|
375
|
+
check(learning_result is not None, f"Learning result: {type(learning_result)}")
|
|
376
|
+
|
|
377
|
+
# Small delay for learning effects
|
|
378
|
+
time.sleep(0.5)
|
|
379
|
+
|
|
380
|
+
# ── 14. Prevention endpoints ───────────────────────────────
|
|
381
|
+
print("\n[14] Testing prevention endpoints...")
|
|
382
|
+
# Check rules
|
|
383
|
+
r = post("/prevention/check", {
|
|
384
|
+
"errorType": "TypeError",
|
|
385
|
+
"message": "Cannot read properties of undefined",
|
|
386
|
+
"projectId": None,
|
|
387
|
+
})
|
|
388
|
+
check(r.status_code == 201, "Prevention check returns 201")
|
|
389
|
+
rules = r.json().get("result")
|
|
390
|
+
check(rules is not None, f"Prevention rules returned: {type(rules)}")
|
|
391
|
+
|
|
392
|
+
# Check antipatterns
|
|
393
|
+
r = post("/prevention/antipatterns", {
|
|
394
|
+
"errorType": "TypeError",
|
|
395
|
+
"message": "Cannot read properties of undefined (reading 'map')",
|
|
396
|
+
})
|
|
397
|
+
check(r.status_code == 201, "Antipatterns check returns 201")
|
|
398
|
+
antipatterns = r.json().get("result")
|
|
399
|
+
check(antipatterns is not None, f"Antipatterns returned: {type(antipatterns)}")
|
|
400
|
+
|
|
401
|
+
# Check code
|
|
402
|
+
r = post("/prevention/code", {
|
|
403
|
+
"source": "const x = obj.prop.nested.deep;\nconsole.log(x.map(i => i.name));",
|
|
404
|
+
"filePath": "test.ts",
|
|
405
|
+
})
|
|
406
|
+
check(r.status_code == 201, "Code prevention check returns 201")
|
|
407
|
+
code_result = r.json().get("result")
|
|
408
|
+
check(code_result is not None, f"Code check returned: {type(code_result)}")
|
|
409
|
+
|
|
410
|
+
# ── 15. Synapse verification ───────────────────────────────
|
|
411
|
+
print("\n[15] Verifying synapses between errors and solutions...")
|
|
412
|
+
if error_ids:
|
|
413
|
+
r = get(f"/synapses/context/{error_ids[0]}")
|
|
414
|
+
check(r.status_code == 200, "Synapse context returns 200")
|
|
415
|
+
ctx = r.json().get("result", {})
|
|
416
|
+
check(isinstance(ctx, dict), "Synapse context is a dict")
|
|
417
|
+
has_solutions = len(ctx.get("solutions", [])) > 0
|
|
418
|
+
check(has_solutions, f"Synapse context has solutions: {len(ctx.get('solutions', []))}")
|
|
419
|
+
|
|
420
|
+
# Synapse stats
|
|
421
|
+
r = get("/synapses/stats")
|
|
422
|
+
check(r.status_code == 200, "Synapse stats returns 200")
|
|
423
|
+
stats = r.json().get("result", {})
|
|
424
|
+
total = stats.get("totalSynapses", 0)
|
|
425
|
+
check(total > 0, f"Synapses exist in network: {total}")
|
|
426
|
+
|
|
427
|
+
# ── Summary ────────────────────────────────────────────────
|
|
428
|
+
print("\n" + "=" * 60)
|
|
429
|
+
total = PASS + FAIL
|
|
430
|
+
print(f" Results: {PASS}/{total} passed, {FAIL} failed")
|
|
431
|
+
if ERRORS:
|
|
432
|
+
print(f"\n Failed tests:")
|
|
433
|
+
for e in ERRORS:
|
|
434
|
+
print(f" - {e}")
|
|
435
|
+
print("=" * 60 + "\n")
|
|
436
|
+
|
|
437
|
+
return 0 if FAIL == 0 else 1
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
if __name__ == "__main__":
|
|
441
|
+
try:
|
|
442
|
+
sys.exit(main())
|
|
443
|
+
except httpx.ConnectError:
|
|
444
|
+
print("\n\033[31mERROR: Cannot connect to Brain daemon on port 7777.\033[0m")
|
|
445
|
+
print("Run 'brain start' or 'brain doctor' first.\n")
|
|
446
|
+
sys.exit(2)
|
|
447
|
+
except Exception as e:
|
|
448
|
+
print(f"\n\033[31mFATAL: {e}\033[0m\n")
|
|
449
|
+
import traceback
|
|
450
|
+
traceback.print_exc()
|
|
451
|
+
sys.exit(2)
|