@timmeck/brain-core 1.2.0 → 1.5.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.
- package/README.md +64 -14
- package/brain.log +6 -0
- package/dist/config/__tests__/loader.test.d.ts +1 -0
- package/dist/config/__tests__/loader.test.js +85 -0
- package/dist/config/__tests__/loader.test.js.map +1 -0
- package/dist/config/loader.d.ts +15 -0
- package/dist/config/loader.js +39 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/cross-brain/__tests__/notifications.test.d.ts +1 -0
- package/dist/cross-brain/__tests__/notifications.test.js +52 -0
- package/dist/cross-brain/__tests__/notifications.test.js.map +1 -0
- package/dist/cross-brain/notifications.d.ts +25 -0
- package/dist/cross-brain/notifications.js +51 -0
- package/dist/cross-brain/notifications.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/learning/__tests__/base-engine.test.d.ts +1 -0
- package/dist/learning/__tests__/base-engine.test.js +49 -0
- package/dist/learning/__tests__/base-engine.test.js.map +1 -0
- package/dist/learning/base-engine.d.ts +16 -0
- package/dist/learning/base-engine.js +30 -0
- package/dist/learning/base-engine.js.map +1 -0
- package/dist/math/__tests__/time-decay.test.d.ts +1 -0
- package/dist/math/__tests__/time-decay.test.js +37 -0
- package/dist/math/__tests__/time-decay.test.js.map +1 -0
- package/dist/math/__tests__/wilson-score.test.d.ts +1 -0
- package/dist/math/__tests__/wilson-score.test.js +43 -0
- package/dist/math/__tests__/wilson-score.test.js.map +1 -0
- package/dist/math/time-decay.d.ts +10 -0
- package/dist/math/time-decay.js +16 -0
- package/dist/math/time-decay.js.map +1 -0
- package/dist/math/wilson-score.d.ts +10 -0
- package/dist/math/wilson-score.js +21 -0
- package/dist/math/wilson-score.js.map +1 -0
- package/dist/research/__tests__/base-engine.test.d.ts +1 -0
- package/dist/research/__tests__/base-engine.test.js +56 -0
- package/dist/research/__tests__/base-engine.test.js.map +1 -0
- package/dist/research/base-engine.d.ts +20 -0
- package/dist/research/base-engine.js +46 -0
- package/dist/research/base-engine.js.map +1 -0
- package/dist/synapses/__tests__/activation.test.d.ts +1 -0
- package/dist/synapses/__tests__/activation.test.js +87 -0
- package/dist/synapses/__tests__/activation.test.js.map +1 -0
- package/dist/synapses/__tests__/decay.test.d.ts +1 -0
- package/dist/synapses/__tests__/decay.test.js +73 -0
- package/dist/synapses/__tests__/decay.test.js.map +1 -0
- package/dist/synapses/__tests__/hebbian.test.d.ts +1 -0
- package/dist/synapses/__tests__/hebbian.test.js +95 -0
- package/dist/synapses/__tests__/hebbian.test.js.map +1 -0
- package/dist/synapses/__tests__/pathfinder.test.d.ts +1 -0
- package/dist/synapses/__tests__/pathfinder.test.js +74 -0
- package/dist/synapses/__tests__/pathfinder.test.js.map +1 -0
- package/dist/synapses/__tests__/synapse-manager.test.d.ts +1 -0
- package/dist/synapses/__tests__/synapse-manager.test.js +94 -0
- package/dist/synapses/__tests__/synapse-manager.test.js.map +1 -0
- package/dist/synapses/activation.d.ts +6 -0
- package/dist/synapses/activation.js +54 -0
- package/dist/synapses/activation.js.map +1 -0
- package/dist/synapses/decay.d.ts +9 -0
- package/dist/synapses/decay.js +26 -0
- package/dist/synapses/decay.js.map +1 -0
- package/dist/synapses/hebbian.d.ts +12 -0
- package/dist/synapses/hebbian.js +45 -0
- package/dist/synapses/hebbian.js.map +1 -0
- package/dist/synapses/pathfinder.d.ts +6 -0
- package/dist/synapses/pathfinder.js +54 -0
- package/dist/synapses/pathfinder.js.map +1 -0
- package/dist/synapses/synapse-manager.d.ts +35 -0
- package/dist/synapses/synapse-manager.js +72 -0
- package/dist/synapses/synapse-manager.js.map +1 -0
- package/dist/synapses/types.d.ts +85 -0
- package/dist/synapses/types.js +7 -0
- package/dist/synapses/types.js.map +1 -0
- package/eslint.config.js +14 -0
- package/package.json +17 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](https://github.com/timmeck/brain-core)
|
|
7
7
|
|
|
8
|
-
**Shared infrastructure for the Brain ecosystem — IPC, MCP, CLI, DB, and utilities.**
|
|
8
|
+
**Shared infrastructure for the Brain ecosystem — IPC, MCP, CLI, DB, math, synapses, and utilities.**
|
|
9
9
|
|
|
10
10
|
Brain Core extracts the common infrastructure used across all Brain MCP servers ([Brain](https://github.com/timmeck/brain), [Trading Brain](https://github.com/timmeck/trading-brain), [Marketing Brain](https://github.com/timmeck/marketing-brain)) into a single, reusable package.
|
|
11
11
|
|
|
@@ -23,6 +23,15 @@ Brain Core extracts the common infrastructure used across all Brain MCP servers
|
|
|
23
23
|
| **CLI Colors** | Shared color palette, formatting helpers (header, table, badges) |
|
|
24
24
|
| **Logger** | Winston-based structured logging with file rotation |
|
|
25
25
|
| **Event Bus** | Generic typed event emitter |
|
|
26
|
+
| **Cross-Brain Client** | Discover and query peer brains over IPC named pipes |
|
|
27
|
+
| **Cross-Brain Notifier** | Push event notifications to peer brains (new in v1.5) |
|
|
28
|
+
| **Math — Wilson Score** | Statistical confidence intervals for win rates / rule confidence |
|
|
29
|
+
| **Math — Time Decay** | Exponential half-life decay for synapse and rule freshness |
|
|
30
|
+
| **Config Loader** | `deepMerge()` + `loadConfigFile()` for layered config |
|
|
31
|
+
| **Synapse Algorithms** | Hebbian learning, decay, spreading activation, A* pathfinding |
|
|
32
|
+
| **BaseSynapseManager** | Abstract synapse manager with strengthen/weaken/activate/findPath/decay |
|
|
33
|
+
| **BaseLearningEngine** | Abstract timer-managed learning engine with error handling |
|
|
34
|
+
| **BaseResearchEngine** | Abstract timer-managed research engine with optional initial delay |
|
|
26
35
|
| **Utils** | Path normalization, data dir resolution, SHA-256 hashing |
|
|
27
36
|
|
|
28
37
|
## Installation
|
|
@@ -120,24 +129,65 @@ class MyRouter implements IpcRouter {
|
|
|
120
129
|
|
|
121
130
|
```
|
|
122
131
|
@timmeck/brain-core
|
|
123
|
-
├── Types
|
|
124
|
-
├── Utils
|
|
125
|
-
├── DB
|
|
126
|
-
├── IPC
|
|
127
|
-
├── MCP
|
|
128
|
-
├── CLI
|
|
129
|
-
|
|
132
|
+
├── Types ──────── IpcMessage, SynapseRecord, NodeRef, NetworkStats
|
|
133
|
+
├── Utils ──────── hash, logger, paths, events
|
|
134
|
+
├── DB ─────────── SQLite connection (WAL mode)
|
|
135
|
+
├── IPC ────────── protocol, server, client
|
|
136
|
+
├── MCP ────────── stdio server, HTTP/SSE server
|
|
137
|
+
├── CLI ────────── colors, formatting helpers
|
|
138
|
+
├── API ────────── BaseApiServer (CORS, auth, RPC, SSE)
|
|
139
|
+
├── Math ───────── Wilson Score, Time Decay
|
|
140
|
+
├── Config ─────── deepMerge, loadConfigFile
|
|
141
|
+
├── Synapses ───── Hebbian, Decay, Activation, Pathfinder, BaseSynapseManager
|
|
142
|
+
├── Learning ───── BaseLearningEngine (abstract, timer-managed)
|
|
143
|
+
├── Research ───── BaseResearchEngine (abstract, timer-managed)
|
|
144
|
+
└── Cross-Brain ── CrossBrainClient, CrossBrainNotifier
|
|
130
145
|
```
|
|
131
146
|
|
|
132
147
|
## Brain Ecosystem
|
|
133
148
|
|
|
134
|
-
| Brain | Purpose | Ports |
|
|
135
|
-
|
|
136
|
-
| [Brain](https://github.com/timmeck/brain) | Error memory & code intelligence | 7777/7778 |
|
|
137
|
-
| [Trading Brain](https://github.com/timmeck/trading-brain) | Adaptive trading intelligence | 7779/7780 |
|
|
138
|
-
| [Marketing Brain](https://github.com/timmeck/marketing-brain) | Content strategy & social media | 7781/7782/7783 |
|
|
149
|
+
| Brain | Version | Purpose | Ports |
|
|
150
|
+
|-------|---------|---------|-------|
|
|
151
|
+
| [Brain](https://github.com/timmeck/brain) | v2.1.0 | Error memory & code intelligence | 7777/7778 |
|
|
152
|
+
| [Trading Brain](https://github.com/timmeck/trading-brain) | v1.2.0 | Adaptive trading intelligence | 7779/7780 |
|
|
153
|
+
| [Marketing Brain](https://github.com/timmeck/marketing-brain) | v0.4.0 | Content strategy & social media | 7781/7782/7783 |
|
|
154
|
+
| [Brain Core](https://github.com/timmeck/brain-core) | v1.6.0 | Shared infrastructure (this package) | — |
|
|
139
155
|
|
|
140
|
-
All three brains are standalone — brain-core is an **optional** shared dependency that eliminates code
|
|
156
|
+
All three brains are standalone — brain-core is an **optional** shared dependency that eliminates ~600 lines of duplicated code across the ecosystem.
|
|
157
|
+
|
|
158
|
+
## Cross-Brain Communication
|
|
159
|
+
|
|
160
|
+
`CrossBrainClient` lets brains discover and query each other over IPC named pipes. Each brain exposes a `status` IPC method returning its name, version, uptime, pid, and method count — enabling automatic peer discovery without central coordination.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { CrossBrainClient, CrossBrainNotifier } from '@timmeck/brain-core';
|
|
164
|
+
|
|
165
|
+
// Query peers
|
|
166
|
+
const cross = new CrossBrainClient('brain');
|
|
167
|
+
const peers = await cross.getAvailablePeers();
|
|
168
|
+
// → [{ name: 'trading-brain', version: '1.2.0', uptime: 3600, pid: 12345, methods: 18 }, ...]
|
|
169
|
+
|
|
170
|
+
// Push event notifications to peers (v1.5+)
|
|
171
|
+
const notifier = new CrossBrainNotifier(cross, 'brain');
|
|
172
|
+
notifier.notify('error:reported', { errorId: 42, fingerprint: 'ENOENT' });
|
|
173
|
+
notifier.notifyPeer('trading-brain', 'insight:created', { insightId: 7 });
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Base Engines
|
|
177
|
+
|
|
178
|
+
Abstract base classes eliminate timer boilerplate from learning and research engines:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { BaseLearningEngine, BaseResearchEngine } from '@timmeck/brain-core';
|
|
182
|
+
|
|
183
|
+
class MyLearningEngine extends BaseLearningEngine {
|
|
184
|
+
runCycle() { /* your learning logic */ }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
class MyResearchEngine extends BaseResearchEngine {
|
|
188
|
+
runCycle() { /* your research logic */ }
|
|
189
|
+
}
|
|
190
|
+
```
|
|
141
191
|
|
|
142
192
|
Visit the [Brain Hub](https://timmeck.github.io/brain-hub/) for the full ecosystem overview.
|
|
143
193
|
|
package/brain.log
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
2026-02-27T15:33:52.574Z [error] {"error":"Error: boom"} Learning cycle error
|
|
2
|
+
2026-02-27T15:33:52.575Z [error] {"error":"Error: boom"} Research cycle error
|
|
3
|
+
2026-02-27T15:36:06.467Z [error] {"error":"Error: boom"} Learning cycle error
|
|
4
|
+
2026-02-27T15:36:06.471Z [error] {"error":"Error: boom"} Research cycle error
|
|
5
|
+
2026-02-27T15:40:43.982Z [error] {"error":"Error: boom"} Research cycle error
|
|
6
|
+
2026-02-27T15:40:43.982Z [error] {"error":"Error: boom"} Learning cycle error
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { deepMerge, loadConfigFile } from '../loader.js';
|
|
4
|
+
describe('deepMerge', () => {
|
|
5
|
+
it('merges flat properties', () => {
|
|
6
|
+
const target = { a: 1, b: 2 };
|
|
7
|
+
deepMerge(target, { b: 3, c: 4 });
|
|
8
|
+
expect(target).toEqual({ a: 1, b: 3, c: 4 });
|
|
9
|
+
});
|
|
10
|
+
it('merges nested objects recursively', () => {
|
|
11
|
+
const target = { api: { port: 7777, enabled: true } };
|
|
12
|
+
deepMerge(target, { api: { port: 8080 } });
|
|
13
|
+
expect(target).toEqual({ api: { port: 8080, enabled: true } });
|
|
14
|
+
});
|
|
15
|
+
it('overwrites arrays (no merge)', () => {
|
|
16
|
+
const target = { tags: ['a', 'b'] };
|
|
17
|
+
deepMerge(target, { tags: ['c'] });
|
|
18
|
+
expect(target).toEqual({ tags: ['c'] });
|
|
19
|
+
});
|
|
20
|
+
it('ignores undefined values', () => {
|
|
21
|
+
const target = { a: 1 };
|
|
22
|
+
deepMerge(target, { a: undefined, b: 2 });
|
|
23
|
+
expect(target).toEqual({ a: 1, b: 2 });
|
|
24
|
+
});
|
|
25
|
+
it('handles deeply nested merge', () => {
|
|
26
|
+
const target = {
|
|
27
|
+
level1: {
|
|
28
|
+
level2: {
|
|
29
|
+
level3: { keep: true, replace: 'old' },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
deepMerge(target, {
|
|
34
|
+
level1: { level2: { level3: { replace: 'new' } } },
|
|
35
|
+
});
|
|
36
|
+
expect(target.level1.level2.level3).toEqual({ keep: true, replace: 'new' });
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe('loadConfigFile', () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
vi.spyOn(fs, 'existsSync');
|
|
42
|
+
vi.spyOn(fs, 'readFileSync');
|
|
43
|
+
});
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
vi.restoreAllMocks();
|
|
46
|
+
});
|
|
47
|
+
it('returns defaults when no file exists', () => {
|
|
48
|
+
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
49
|
+
const defaults = { port: 8080, name: 'test' };
|
|
50
|
+
const result = loadConfigFile(defaults);
|
|
51
|
+
expect(result).toEqual(defaults);
|
|
52
|
+
});
|
|
53
|
+
it('merges config file into defaults', () => {
|
|
54
|
+
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
55
|
+
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ port: 9090 }));
|
|
56
|
+
const defaults = { port: 8080, name: 'test' };
|
|
57
|
+
const result = loadConfigFile(defaults, '/some/config.json');
|
|
58
|
+
expect(result).toEqual({ port: 9090, name: 'test' });
|
|
59
|
+
});
|
|
60
|
+
it('uses defaultConfigPath as fallback', () => {
|
|
61
|
+
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
62
|
+
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ name: 'custom' }));
|
|
63
|
+
const defaults = { port: 8080, name: 'test' };
|
|
64
|
+
const result = loadConfigFile(defaults, undefined, '/default/config.json');
|
|
65
|
+
expect(result).toEqual({ port: 8080, name: 'custom' });
|
|
66
|
+
expect(fs.existsSync).toHaveBeenCalledWith('/default/config.json');
|
|
67
|
+
});
|
|
68
|
+
it('does not mutate original defaults', () => {
|
|
69
|
+
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
70
|
+
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ port: 9090 }));
|
|
71
|
+
const defaults = { port: 8080, name: 'test' };
|
|
72
|
+
loadConfigFile(defaults, '/some/config.json');
|
|
73
|
+
expect(defaults.port).toBe(8080);
|
|
74
|
+
});
|
|
75
|
+
it('handles nested config merge', () => {
|
|
76
|
+
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
77
|
+
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({
|
|
78
|
+
api: { port: 9999 },
|
|
79
|
+
}));
|
|
80
|
+
const defaults = { api: { port: 8080, enabled: true } };
|
|
81
|
+
const result = loadConfigFile(defaults, '/some/config.json');
|
|
82
|
+
expect(result).toEqual({ api: { port: 9999, enabled: true } });
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
//# sourceMappingURL=loader.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.test.js","sourceRoot":"","sources":["../../../src/config/__tests__/loader.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEzD,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAA6B,CAAC;QACzD,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAClC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAA6B,CAAC;QACjF,SAAS,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;QAC3C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAA6B,CAAC;QAC/D,SAAS,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,EAA6B,CAAC;QACnD,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,MAAM,GAAG;YACb,MAAM,EAAE;gBACN,MAAM,EAAE;oBACN,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE;iBACvC;aACF;SACyB,CAAC;QAC7B,SAAS,CAAC,MAAM,EAAE;YAChB,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE;SACnD,CAAC,CAAC;QACH,MAAM,CAAE,MAAc,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IACvF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;QAC3B,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,eAAe,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QAEhD,MAAM,QAAQ,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAC9C,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;QACxC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QAC/C,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAE3E,MAAM,QAAQ,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAC9C,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC;QAC7D,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QAC/C,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QAE/E,MAAM,QAAQ,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAC9C,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,sBAAsB,CAAC,CAAC;QAC3E,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QACvD,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,oBAAoB,CAAC,sBAAsB,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QAC/C,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAE3E,MAAM,QAAQ,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAC9C,cAAc,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC;QAC9C,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QAC/C,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC;YACxD,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;SACpB,CAAC,CAAC,CAAC;QAEJ,MAAM,QAAQ,GAAG,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;QACxD,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC;QAC7D,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep-merge source into target. Recursively merges nested objects,
|
|
3
|
+
* overwrites primitives and arrays.
|
|
4
|
+
*/
|
|
5
|
+
export declare function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): void;
|
|
6
|
+
/**
|
|
7
|
+
* Load and parse a JSON config file, merging it into defaults.
|
|
8
|
+
* If the file doesn't exist, returns the defaults unchanged.
|
|
9
|
+
*
|
|
10
|
+
* @param defaults The default config object (will be cloned)
|
|
11
|
+
* @param configPath Explicit config file path, or undefined for auto-detect
|
|
12
|
+
* @param defaultConfigPath Fallback path when configPath is not provided
|
|
13
|
+
* @returns Merged config object
|
|
14
|
+
*/
|
|
15
|
+
export declare function loadConfigFile<T>(defaults: T, configPath?: string, defaultConfigPath?: string): T;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Deep-merge source into target. Recursively merges nested objects,
|
|
5
|
+
* overwrites primitives and arrays.
|
|
6
|
+
*/
|
|
7
|
+
export function deepMerge(target, source) {
|
|
8
|
+
for (const key of Object.keys(source)) {
|
|
9
|
+
const val = source[key];
|
|
10
|
+
if (val && typeof val === 'object' && !Array.isArray(val) && target[key] && typeof target[key] === 'object') {
|
|
11
|
+
deepMerge(target[key], val);
|
|
12
|
+
}
|
|
13
|
+
else if (val !== undefined) {
|
|
14
|
+
target[key] = val;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Load and parse a JSON config file, merging it into defaults.
|
|
20
|
+
* If the file doesn't exist, returns the defaults unchanged.
|
|
21
|
+
*
|
|
22
|
+
* @param defaults The default config object (will be cloned)
|
|
23
|
+
* @param configPath Explicit config file path, or undefined for auto-detect
|
|
24
|
+
* @param defaultConfigPath Fallback path when configPath is not provided
|
|
25
|
+
* @returns Merged config object
|
|
26
|
+
*/
|
|
27
|
+
export function loadConfigFile(defaults, configPath, defaultConfigPath) {
|
|
28
|
+
const config = structuredClone(defaults);
|
|
29
|
+
const filePath = configPath
|
|
30
|
+
? path.resolve(configPath)
|
|
31
|
+
: defaultConfigPath;
|
|
32
|
+
if (filePath && fs.existsSync(filePath)) {
|
|
33
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
34
|
+
const fileConfig = JSON.parse(raw);
|
|
35
|
+
deepMerge(config, fileConfig);
|
|
36
|
+
}
|
|
37
|
+
return config;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,MAA+B,EAAE,MAA+B;IACxF,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACxB,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC5G,SAAS,CAAC,MAAM,CAAC,GAAG,CAA4B,EAAE,GAA8B,CAAC,CAAC;QACpF,CAAC;aAAM,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;QACpB,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAW,EACX,UAAmB,EACnB,iBAA0B;IAE1B,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IAEzC,MAAM,QAAQ,GAAG,UAAU;QACzB,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC;QAC1B,CAAC,CAAC,iBAAiB,CAAC;IAEtB,IAAI,QAAQ,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC/C,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAe,CAAC;QACjD,SAAS,CAAC,MAA4C,EAAE,UAAgD,CAAC,CAAC;IAC5G,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
vi.mock('../../utils/logger.js', () => ({
|
|
3
|
+
getLogger: () => ({
|
|
4
|
+
debug: vi.fn(),
|
|
5
|
+
info: vi.fn(),
|
|
6
|
+
warn: vi.fn(),
|
|
7
|
+
error: vi.fn(),
|
|
8
|
+
}),
|
|
9
|
+
}));
|
|
10
|
+
import { CrossBrainNotifier } from '../notifications.js';
|
|
11
|
+
import { CrossBrainClient } from '../client.js';
|
|
12
|
+
describe('CrossBrainNotifier', () => {
|
|
13
|
+
let client;
|
|
14
|
+
let notifier;
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
client = new CrossBrainClient('brain');
|
|
17
|
+
vi.spyOn(client, 'broadcast').mockResolvedValue([]);
|
|
18
|
+
vi.spyOn(client, 'query').mockResolvedValue(null);
|
|
19
|
+
notifier = new CrossBrainNotifier(client, 'brain');
|
|
20
|
+
});
|
|
21
|
+
it('broadcasts notifications to all peers', async () => {
|
|
22
|
+
await notifier.notify('error:reported', { errorId: 1 });
|
|
23
|
+
expect(client.broadcast).toHaveBeenCalledWith('cross-brain.notify', expect.objectContaining({
|
|
24
|
+
source: 'brain',
|
|
25
|
+
event: 'error:reported',
|
|
26
|
+
data: { errorId: 1 },
|
|
27
|
+
}));
|
|
28
|
+
});
|
|
29
|
+
it('sends targeted notification to specific peer', async () => {
|
|
30
|
+
await notifier.notifyPeer('trading-brain', 'insight:created', { insightId: 5 });
|
|
31
|
+
expect(client.query).toHaveBeenCalledWith('trading-brain', 'cross-brain.notify', expect.objectContaining({
|
|
32
|
+
source: 'brain',
|
|
33
|
+
event: 'insight:created',
|
|
34
|
+
data: { insightId: 5 },
|
|
35
|
+
}));
|
|
36
|
+
});
|
|
37
|
+
it('includes timestamp in payload', async () => {
|
|
38
|
+
await notifier.notify('test', {});
|
|
39
|
+
const payload = client.broadcast.mock.calls[0][1];
|
|
40
|
+
expect(payload.timestamp).toBeDefined();
|
|
41
|
+
expect(new Date(payload.timestamp).getTime()).toBeGreaterThan(0);
|
|
42
|
+
});
|
|
43
|
+
it('does not throw when broadcast fails', async () => {
|
|
44
|
+
vi.spyOn(client, 'broadcast').mockRejectedValue(new Error('offline'));
|
|
45
|
+
await expect(notifier.notify('test', {})).resolves.toBeUndefined();
|
|
46
|
+
});
|
|
47
|
+
it('does not throw when peer query fails', async () => {
|
|
48
|
+
vi.spyOn(client, 'query').mockRejectedValue(new Error('offline'));
|
|
49
|
+
await expect(notifier.notifyPeer('trading-brain', 'test', {})).resolves.toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
//# sourceMappingURL=notifications.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notifications.test.js","sourceRoot":"","sources":["../../../src/cross-brain/__tests__/notifications.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { CrossBrainClient } from './client.js';
|
|
2
|
+
export interface CrossBrainEvent {
|
|
3
|
+
source: string;
|
|
4
|
+
event: string;
|
|
5
|
+
data: unknown;
|
|
6
|
+
timestamp: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Cross-Brain Notifier — sends event notifications to peer brains.
|
|
10
|
+
* Built on top of CrossBrainClient's query/broadcast infrastructure.
|
|
11
|
+
*/
|
|
12
|
+
export declare class CrossBrainNotifier {
|
|
13
|
+
private client;
|
|
14
|
+
private selfName;
|
|
15
|
+
private logger;
|
|
16
|
+
constructor(client: CrossBrainClient, selfName: string);
|
|
17
|
+
/**
|
|
18
|
+
* Notify all peers about an event.
|
|
19
|
+
*/
|
|
20
|
+
notify(event: string, data: unknown): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Notify a specific peer about an event.
|
|
23
|
+
*/
|
|
24
|
+
notifyPeer(peerName: string, event: string, data: unknown): Promise<void>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { getLogger } from '../utils/logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-Brain Notifier — sends event notifications to peer brains.
|
|
4
|
+
* Built on top of CrossBrainClient's query/broadcast infrastructure.
|
|
5
|
+
*/
|
|
6
|
+
export class CrossBrainNotifier {
|
|
7
|
+
client;
|
|
8
|
+
selfName;
|
|
9
|
+
logger = getLogger();
|
|
10
|
+
constructor(client, selfName) {
|
|
11
|
+
this.client = client;
|
|
12
|
+
this.selfName = selfName;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Notify all peers about an event.
|
|
16
|
+
*/
|
|
17
|
+
async notify(event, data) {
|
|
18
|
+
const payload = {
|
|
19
|
+
source: this.selfName,
|
|
20
|
+
event,
|
|
21
|
+
data,
|
|
22
|
+
timestamp: new Date().toISOString(),
|
|
23
|
+
};
|
|
24
|
+
try {
|
|
25
|
+
await this.client.broadcast('cross-brain.notify', payload);
|
|
26
|
+
this.logger.debug(`Cross-brain notification sent: ${event}`);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
this.logger.debug(`Cross-brain notification failed (peers may be offline): ${event}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Notify a specific peer about an event.
|
|
34
|
+
*/
|
|
35
|
+
async notifyPeer(peerName, event, data) {
|
|
36
|
+
const payload = {
|
|
37
|
+
source: this.selfName,
|
|
38
|
+
event,
|
|
39
|
+
data,
|
|
40
|
+
timestamp: new Date().toISOString(),
|
|
41
|
+
};
|
|
42
|
+
try {
|
|
43
|
+
await this.client.query(peerName, 'cross-brain.notify', payload);
|
|
44
|
+
this.logger.debug(`Cross-brain notification sent to ${peerName}: ${event}`);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
this.logger.debug(`Cross-brain notification to ${peerName} failed: ${event}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=notifications.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../src/cross-brain/notifications.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAS/C;;;GAGG;AACH,MAAM,OAAO,kBAAkB;IAGT;IAAkC;IAF9C,MAAM,GAAG,SAAS,EAAE,CAAC;IAE7B,YAAoB,MAAwB,EAAU,QAAgB;QAAlD,WAAM,GAAN,MAAM,CAAkB;QAAU,aAAQ,GAAR,QAAQ,CAAQ;IAAG,CAAC;IAE1E;;OAEG;IACH,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,IAAa;QACvC,MAAM,OAAO,GAAoB;YAC/B,MAAM,EAAE,IAAI,CAAC,QAAQ;YACrB,KAAK;YACL,IAAI;YACJ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC;QAEF,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAC;YAC3D,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,kCAAkC,KAAK,EAAE,CAAC,CAAC;QAC/D,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,2DAA2D,KAAK,EAAE,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU,CAAC,QAAgB,EAAE,KAAa,EAAE,IAAa;QAC7D,MAAM,OAAO,GAAoB;YAC/B,MAAM,EAAE,IAAI,CAAC,QAAQ;YACrB,KAAK;YACL,IAAI;YACJ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC;QAEF,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,oBAAoB,EAAE,OAAO,CAAC,CAAC;YACjE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,oCAAoC,QAAQ,KAAK,KAAK,EAAE,CAAC,CAAC;QAC9E,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B,QAAQ,YAAY,KAAK,EAAE,CAAC,CAAC;QAChF,CAAC;IACH,CAAC;CACF"}
|
package/dist/index.d.ts
CHANGED
|
@@ -16,5 +16,21 @@ export type { McpHttpServerOptions } from './mcp/http-server.js';
|
|
|
16
16
|
export { c, baseIcons, header, keyValue, statusBadge, progressBar, divider, table, stripAnsi } from './cli/colors.js';
|
|
17
17
|
export { BaseApiServer } from './api/server.js';
|
|
18
18
|
export type { ApiServerOptions, RouteDefinition } from './api/server.js';
|
|
19
|
+
export { wilsonScore } from './math/wilson-score.js';
|
|
20
|
+
export { timeDecayFactor } from './math/time-decay.js';
|
|
21
|
+
export { deepMerge, loadConfigFile } from './config/loader.js';
|
|
22
|
+
export type { NodeRef, SynapseRecord, ActivationResult, PathNode, SynapsePath, NetworkStats, HebbianConfig, DecayConfig, SynapseRepoInterface, } from './synapses/types.js';
|
|
23
|
+
export type { SynapseManagerConfig } from './synapses/synapse-manager.js';
|
|
24
|
+
export { strengthen, weaken } from './synapses/hebbian.js';
|
|
25
|
+
export { decayAll } from './synapses/decay.js';
|
|
26
|
+
export { spreadingActivation } from './synapses/activation.js';
|
|
27
|
+
export { findPath } from './synapses/pathfinder.js';
|
|
28
|
+
export { BaseSynapseManager } from './synapses/synapse-manager.js';
|
|
29
|
+
export { BaseLearningEngine } from './learning/base-engine.js';
|
|
30
|
+
export type { LearningEngineConfig } from './learning/base-engine.js';
|
|
31
|
+
export { BaseResearchEngine } from './research/base-engine.js';
|
|
32
|
+
export type { ResearchEngineConfig } from './research/base-engine.js';
|
|
19
33
|
export { CrossBrainClient } from './cross-brain/client.js';
|
|
20
34
|
export type { BrainPeer } from './cross-brain/client.js';
|
|
35
|
+
export { CrossBrainNotifier } from './cross-brain/notifications.js';
|
|
36
|
+
export type { CrossBrainEvent } from './cross-brain/notifications.js';
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,20 @@ export { McpHttpServer } from './mcp/http-server.js';
|
|
|
16
16
|
export { c, baseIcons, header, keyValue, statusBadge, progressBar, divider, table, stripAnsi } from './cli/colors.js';
|
|
17
17
|
// ── API ────────────────────────────────────────────────────
|
|
18
18
|
export { BaseApiServer } from './api/server.js';
|
|
19
|
+
// ── Math ───────────────────────────────────────────────────
|
|
20
|
+
export { wilsonScore } from './math/wilson-score.js';
|
|
21
|
+
export { timeDecayFactor } from './math/time-decay.js';
|
|
22
|
+
// ── Config ─────────────────────────────────────────────────
|
|
23
|
+
export { deepMerge, loadConfigFile } from './config/loader.js';
|
|
24
|
+
export { strengthen, weaken } from './synapses/hebbian.js';
|
|
25
|
+
export { decayAll } from './synapses/decay.js';
|
|
26
|
+
export { spreadingActivation } from './synapses/activation.js';
|
|
27
|
+
export { findPath } from './synapses/pathfinder.js';
|
|
28
|
+
export { BaseSynapseManager } from './synapses/synapse-manager.js';
|
|
29
|
+
// ── Engines ───────────────────────────────────────────────
|
|
30
|
+
export { BaseLearningEngine } from './learning/base-engine.js';
|
|
31
|
+
export { BaseResearchEngine } from './research/base-engine.js';
|
|
19
32
|
// ── Cross-Brain ────────────────────────────────────────────
|
|
20
33
|
export { CrossBrainClient } from './cross-brain/client.js';
|
|
34
|
+
export { CrossBrainNotifier } from './cross-brain/notifications.js';
|
|
21
35
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,8DAA8D;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEzE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,8DAA8D;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,8DAA8D;AAC9D,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,8DAA8D;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,8DAA8D;AAC9D,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEtH,8DAA8D;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,8DAA8D;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,8DAA8D;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEzE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,8DAA8D;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,8DAA8D;AAC9D,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,8DAA8D;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,8DAA8D;AAC9D,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEtH,8DAA8D;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,8DAA8D;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,8DAA8D;AAC9D,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAQ/D,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAEnE,6DAA6D;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAG/D,8DAA8D;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { BaseLearningEngine } from '../base-engine.js';
|
|
3
|
+
class TestLearningEngine extends BaseLearningEngine {
|
|
4
|
+
cycleCount = 0;
|
|
5
|
+
runCycle() {
|
|
6
|
+
this.cycleCount++;
|
|
7
|
+
return { count: this.cycleCount };
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
describe('BaseLearningEngine', () => {
|
|
11
|
+
beforeEach(() => { vi.useFakeTimers(); });
|
|
12
|
+
afterEach(() => { vi.useRealTimers(); });
|
|
13
|
+
it('should run cycles at the configured interval', () => {
|
|
14
|
+
const engine = new TestLearningEngine({ intervalMs: 1000 });
|
|
15
|
+
engine.start();
|
|
16
|
+
expect(engine.cycleCount).toBe(0);
|
|
17
|
+
vi.advanceTimersByTime(1000);
|
|
18
|
+
expect(engine.cycleCount).toBe(1);
|
|
19
|
+
vi.advanceTimersByTime(2000);
|
|
20
|
+
expect(engine.cycleCount).toBe(3);
|
|
21
|
+
engine.stop();
|
|
22
|
+
});
|
|
23
|
+
it('should stop the timer', () => {
|
|
24
|
+
const engine = new TestLearningEngine({ intervalMs: 500 });
|
|
25
|
+
engine.start();
|
|
26
|
+
vi.advanceTimersByTime(500);
|
|
27
|
+
expect(engine.cycleCount).toBe(1);
|
|
28
|
+
engine.stop();
|
|
29
|
+
vi.advanceTimersByTime(2000);
|
|
30
|
+
expect(engine.cycleCount).toBe(1);
|
|
31
|
+
});
|
|
32
|
+
it('should handle errors in runCycle gracefully', () => {
|
|
33
|
+
class FailingEngine extends BaseLearningEngine {
|
|
34
|
+
runCycle() { throw new Error('boom'); }
|
|
35
|
+
}
|
|
36
|
+
const engine = new FailingEngine({ intervalMs: 100 });
|
|
37
|
+
engine.start();
|
|
38
|
+
// Should not throw
|
|
39
|
+
expect(() => vi.advanceTimersByTime(100)).not.toThrow();
|
|
40
|
+
engine.stop();
|
|
41
|
+
});
|
|
42
|
+
it('should be safe to call stop multiple times', () => {
|
|
43
|
+
const engine = new TestLearningEngine({ intervalMs: 1000 });
|
|
44
|
+
engine.start();
|
|
45
|
+
engine.stop();
|
|
46
|
+
engine.stop(); // No error
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
//# sourceMappingURL=base-engine.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-engine.test.js","sourceRoot":"","sources":["../../../src/learning/__tests__/base-engine.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface LearningEngineConfig {
|
|
2
|
+
intervalMs: number;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Abstract base class for learning engines.
|
|
6
|
+
* Handles timer lifecycle — subclasses implement runCycle().
|
|
7
|
+
*/
|
|
8
|
+
export declare abstract class BaseLearningEngine {
|
|
9
|
+
protected engineConfig: LearningEngineConfig;
|
|
10
|
+
protected timer: ReturnType<typeof setInterval> | null;
|
|
11
|
+
protected logger: import("winston").Logger;
|
|
12
|
+
constructor(engineConfig: LearningEngineConfig);
|
|
13
|
+
start(): void;
|
|
14
|
+
stop(): void;
|
|
15
|
+
abstract runCycle(): unknown;
|
|
16
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getLogger } from '../utils/logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* Abstract base class for learning engines.
|
|
4
|
+
* Handles timer lifecycle — subclasses implement runCycle().
|
|
5
|
+
*/
|
|
6
|
+
export class BaseLearningEngine {
|
|
7
|
+
engineConfig;
|
|
8
|
+
timer = null;
|
|
9
|
+
logger = getLogger();
|
|
10
|
+
constructor(engineConfig) {
|
|
11
|
+
this.engineConfig = engineConfig;
|
|
12
|
+
}
|
|
13
|
+
start() {
|
|
14
|
+
this.timer = setInterval(() => {
|
|
15
|
+
try {
|
|
16
|
+
this.runCycle();
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
this.logger.error('Learning cycle error', { error: String(err) });
|
|
20
|
+
}
|
|
21
|
+
}, this.engineConfig.intervalMs);
|
|
22
|
+
}
|
|
23
|
+
stop() {
|
|
24
|
+
if (this.timer) {
|
|
25
|
+
clearInterval(this.timer);
|
|
26
|
+
this.timer = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=base-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-engine.js","sourceRoot":"","sources":["../../src/learning/base-engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAM/C;;;GAGG;AACH,MAAM,OAAgB,kBAAkB;IAIhB;IAHZ,KAAK,GAA0C,IAAI,CAAC;IACpD,MAAM,GAAG,SAAS,EAAE,CAAC;IAE/B,YAAsB,YAAkC;QAAlC,iBAAY,GAAZ,YAAY,CAAsB;IAAG,CAAC;IAE5D,KAAK;QACH,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC;gBACH,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACpE,CAAC;QACH,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;IACnC,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;IACH,CAAC;CAGF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
+
import { timeDecayFactor } from '../time-decay.js';
|
|
3
|
+
describe('timeDecayFactor', () => {
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
vi.restoreAllMocks();
|
|
6
|
+
});
|
|
7
|
+
it('returns ~1.0 for just-activated item', () => {
|
|
8
|
+
const now = new Date().toISOString();
|
|
9
|
+
const factor = timeDecayFactor(now, 30);
|
|
10
|
+
expect(factor).toBeGreaterThan(0.99);
|
|
11
|
+
expect(factor).toBeLessThanOrEqual(1.0);
|
|
12
|
+
});
|
|
13
|
+
it('returns ~0.5 at exactly one half-life', () => {
|
|
14
|
+
const halfLifeDays = 30;
|
|
15
|
+
const activatedAt = new Date(Date.now() - halfLifeDays * 24 * 60 * 60 * 1000).toISOString();
|
|
16
|
+
const factor = timeDecayFactor(activatedAt, halfLifeDays);
|
|
17
|
+
expect(factor).toBeCloseTo(0.5, 1);
|
|
18
|
+
});
|
|
19
|
+
it('returns ~0.25 at two half-lives', () => {
|
|
20
|
+
const halfLifeDays = 14;
|
|
21
|
+
const activatedAt = new Date(Date.now() - 28 * 24 * 60 * 60 * 1000).toISOString();
|
|
22
|
+
const factor = timeDecayFactor(activatedAt, halfLifeDays);
|
|
23
|
+
expect(factor).toBeCloseTo(0.25, 1);
|
|
24
|
+
});
|
|
25
|
+
it('returns very small value for very old items', () => {
|
|
26
|
+
const activatedAt = new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString();
|
|
27
|
+
const factor = timeDecayFactor(activatedAt, 30);
|
|
28
|
+
expect(factor).toBeLessThan(0.01);
|
|
29
|
+
});
|
|
30
|
+
it('works with different half-life values', () => {
|
|
31
|
+
const activatedAt = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
32
|
+
const shortHalfLife = timeDecayFactor(activatedAt, 7);
|
|
33
|
+
const longHalfLife = timeDecayFactor(activatedAt, 45);
|
|
34
|
+
expect(shortHalfLife).toBeLessThan(longHalfLife);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=time-decay.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-decay.test.js","sourceRoot":"","sources":["../../../src/math/__tests__/time-decay.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,eAAe,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrC,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACxC,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,YAAY,GAAG,EAAE,CAAC;QACxB,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QAC5F,MAAM,MAAM,GAAG,eAAe,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;QAC1D,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,YAAY,GAAG,EAAE,CAAC;QACxB,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QAClF,MAAM,MAAM,GAAG,eAAe,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;QAC1D,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QACnF,MAAM,MAAM,GAAG,eAAe,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAChD,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QACjF,MAAM,aAAa,GAAG,eAAe,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QACtD,MAAM,YAAY,GAAG,eAAe,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QACtD,MAAM,CAAC,aAAa,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|