@timmeck/brain-core 2.36.56 → 2.36.58

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 (152) hide show
  1. package/README.md +6 -0
  2. package/command-center.html +133 -2
  3. package/dist/action/__tests__/action-bridge.test.d.ts +1 -0
  4. package/dist/action/__tests__/action-bridge.test.js +145 -0
  5. package/dist/action/__tests__/action-bridge.test.js.map +1 -0
  6. package/dist/action/__tests__/content-handler.test.d.ts +1 -0
  7. package/dist/action/__tests__/content-handler.test.js +100 -0
  8. package/dist/action/__tests__/content-handler.test.js.map +1 -0
  9. package/dist/action/__tests__/trade-handler.test.d.ts +1 -0
  10. package/dist/action/__tests__/trade-handler.test.js +165 -0
  11. package/dist/action/__tests__/trade-handler.test.js.map +1 -0
  12. package/dist/action/action-bridge.d.ts +95 -0
  13. package/dist/action/action-bridge.js +242 -0
  14. package/dist/action/action-bridge.js.map +1 -0
  15. package/dist/action/handlers/content-handler.d.ts +23 -0
  16. package/dist/action/handlers/content-handler.js +30 -0
  17. package/dist/action/handlers/content-handler.js.map +1 -0
  18. package/dist/action/handlers/index.d.ts +4 -0
  19. package/dist/action/handlers/index.js +3 -0
  20. package/dist/action/handlers/index.js.map +1 -0
  21. package/dist/action/handlers/trade-handler.d.ts +43 -0
  22. package/dist/action/handlers/trade-handler.js +43 -0
  23. package/dist/action/handlers/trade-handler.js.map +1 -0
  24. package/dist/action/index.d.ts +4 -0
  25. package/dist/action/index.js +3 -0
  26. package/dist/action/index.js.map +1 -0
  27. package/dist/agent-training/__tests__/agent-trainer.test.d.ts +1 -0
  28. package/dist/agent-training/__tests__/agent-trainer.test.js +158 -0
  29. package/dist/agent-training/__tests__/agent-trainer.test.js.map +1 -0
  30. package/dist/agent-training/__tests__/sub-agent-factory.test.d.ts +1 -0
  31. package/dist/agent-training/__tests__/sub-agent-factory.test.js +100 -0
  32. package/dist/agent-training/__tests__/sub-agent-factory.test.js.map +1 -0
  33. package/dist/agent-training/__tests__/sub-agent.test.d.ts +1 -0
  34. package/dist/agent-training/__tests__/sub-agent.test.js +102 -0
  35. package/dist/agent-training/__tests__/sub-agent.test.js.map +1 -0
  36. package/dist/agent-training/index.d.ts +4 -0
  37. package/dist/agent-training/index.js +2 -0
  38. package/dist/agent-training/index.js.map +1 -1
  39. package/dist/agent-training/sub-agent-factory.d.ts +36 -0
  40. package/dist/agent-training/sub-agent-factory.js +128 -0
  41. package/dist/agent-training/sub-agent-factory.js.map +1 -0
  42. package/dist/agent-training/sub-agent.d.ts +57 -0
  43. package/dist/agent-training/sub-agent.js +135 -0
  44. package/dist/agent-training/sub-agent.js.map +1 -0
  45. package/dist/chat/__tests__/chat-engine.test.d.ts +1 -0
  46. package/dist/chat/__tests__/chat-engine.test.js +128 -0
  47. package/dist/chat/__tests__/chat-engine.test.js.map +1 -0
  48. package/dist/chat/chat-engine.d.ts +55 -0
  49. package/dist/chat/chat-engine.js +186 -0
  50. package/dist/chat/chat-engine.js.map +1 -0
  51. package/dist/chat/index.d.ts +2 -0
  52. package/dist/chat/index.js +2 -0
  53. package/dist/chat/index.js.map +1 -0
  54. package/dist/codegen/__tests__/code-forge.test.d.ts +1 -0
  55. package/dist/codegen/__tests__/code-forge.test.js +105 -0
  56. package/dist/codegen/__tests__/code-forge.test.js.map +1 -0
  57. package/dist/codegen/code-forge.d.ts +87 -0
  58. package/dist/codegen/code-forge.js +211 -0
  59. package/dist/codegen/code-forge.js.map +1 -0
  60. package/dist/codegen/index.d.ts +2 -0
  61. package/dist/codegen/index.js +1 -0
  62. package/dist/codegen/index.js.map +1 -1
  63. package/dist/content/__tests__/auto-publisher.test.d.ts +1 -0
  64. package/dist/content/__tests__/auto-publisher.test.js +125 -0
  65. package/dist/content/__tests__/auto-publisher.test.js.map +1 -0
  66. package/dist/content/__tests__/content-forge.test.d.ts +1 -0
  67. package/dist/content/__tests__/content-forge.test.js +117 -0
  68. package/dist/content/__tests__/content-forge.test.js.map +1 -0
  69. package/dist/content/auto-publisher.d.ts +51 -0
  70. package/dist/content/auto-publisher.js +147 -0
  71. package/dist/content/auto-publisher.js.map +1 -0
  72. package/dist/content/content-forge.d.ts +106 -0
  73. package/dist/content/content-forge.js +232 -0
  74. package/dist/content/content-forge.js.map +1 -0
  75. package/dist/content/index.d.ts +4 -0
  76. package/dist/content/index.js +3 -0
  77. package/dist/content/index.js.map +1 -0
  78. package/dist/creative/__tests__/creative-engine.test.d.ts +1 -0
  79. package/dist/creative/__tests__/creative-engine.test.js +151 -0
  80. package/dist/creative/__tests__/creative-engine.test.js.map +1 -0
  81. package/dist/cross-brain/__tests__/signal-router.test.d.ts +1 -0
  82. package/dist/cross-brain/__tests__/signal-router.test.js +159 -0
  83. package/dist/cross-brain/__tests__/signal-router.test.js.map +1 -0
  84. package/dist/cross-brain/signal-router.d.ts +57 -0
  85. package/dist/cross-brain/signal-router.js +148 -0
  86. package/dist/cross-brain/signal-router.js.map +1 -0
  87. package/dist/dashboard/__tests__/command-center-server.test.js +31 -0
  88. package/dist/dashboard/__tests__/command-center-server.test.js.map +1 -1
  89. package/dist/dashboard/command-center-server.d.ts +11 -0
  90. package/dist/dashboard/command-center-server.js +72 -0
  91. package/dist/dashboard/command-center-server.js.map +1 -1
  92. package/dist/dream/__tests__/dream-engine.test.d.ts +1 -0
  93. package/dist/dream/__tests__/dream-engine.test.js +184 -0
  94. package/dist/dream/__tests__/dream-engine.test.js.map +1 -0
  95. package/dist/feedback/__tests__/feedback-router.test.d.ts +1 -0
  96. package/dist/feedback/__tests__/feedback-router.test.js +173 -0
  97. package/dist/feedback/__tests__/feedback-router.test.js.map +1 -0
  98. package/dist/feedback/feedback-router.d.ts +53 -0
  99. package/dist/feedback/feedback-router.js +193 -0
  100. package/dist/feedback/feedback-router.js.map +1 -0
  101. package/dist/feedback/index.d.ts +2 -0
  102. package/dist/feedback/index.js +1 -0
  103. package/dist/feedback/index.js.map +1 -1
  104. package/dist/goals/__tests__/goal-engine.test.d.ts +1 -0
  105. package/dist/goals/__tests__/goal-engine.test.js +203 -0
  106. package/dist/goals/__tests__/goal-engine.test.js.map +1 -0
  107. package/dist/guardrails/__tests__/guardrail-engine.test.d.ts +1 -0
  108. package/dist/guardrails/__tests__/guardrail-engine.test.js +343 -0
  109. package/dist/guardrails/__tests__/guardrail-engine.test.js.map +1 -0
  110. package/dist/index.d.ts +16 -0
  111. package/dist/index.js +13 -0
  112. package/dist/index.js.map +1 -1
  113. package/dist/metacognition/__tests__/auto-experiment-engine.test.d.ts +1 -0
  114. package/dist/metacognition/__tests__/auto-experiment-engine.test.js +218 -0
  115. package/dist/metacognition/__tests__/auto-experiment-engine.test.js.map +1 -0
  116. package/dist/metacognition/__tests__/evolution-engine.test.d.ts +1 -0
  117. package/dist/metacognition/__tests__/evolution-engine.test.js +473 -0
  118. package/dist/metacognition/__tests__/evolution-engine.test.js.map +1 -0
  119. package/dist/metacognition/__tests__/parameter-registry.test.d.ts +1 -0
  120. package/dist/metacognition/__tests__/parameter-registry.test.js +223 -0
  121. package/dist/metacognition/__tests__/parameter-registry.test.js.map +1 -0
  122. package/dist/prediction/__tests__/prediction-engine.test.d.ts +1 -0
  123. package/dist/prediction/__tests__/prediction-engine.test.js +244 -0
  124. package/dist/prediction/__tests__/prediction-engine.test.js.map +1 -0
  125. package/dist/research/research-orchestrator.d.ts +12 -0
  126. package/dist/research/research-orchestrator.js +109 -0
  127. package/dist/research/research-orchestrator.js.map +1 -1
  128. package/dist/sandbox/__tests__/code-sandbox.test.d.ts +1 -0
  129. package/dist/sandbox/__tests__/code-sandbox.test.js +182 -0
  130. package/dist/sandbox/__tests__/code-sandbox.test.js.map +1 -0
  131. package/dist/scanner/__tests__/signal-scanner.test.d.ts +1 -0
  132. package/dist/scanner/__tests__/signal-scanner.test.js +142 -0
  133. package/dist/scanner/__tests__/signal-scanner.test.js.map +1 -0
  134. package/dist/self-modification/__tests__/self-modification-engine.test.d.ts +1 -0
  135. package/dist/self-modification/__tests__/self-modification-engine.test.js +255 -0
  136. package/dist/self-modification/__tests__/self-modification-engine.test.js.map +1 -0
  137. package/dist/strategy/__tests__/strategy-forge.test.d.ts +1 -0
  138. package/dist/strategy/__tests__/strategy-forge.test.js +190 -0
  139. package/dist/strategy/__tests__/strategy-forge.test.js.map +1 -0
  140. package/dist/strategy/__tests__/strategy-mutator.test.d.ts +1 -0
  141. package/dist/strategy/__tests__/strategy-mutator.test.js +141 -0
  142. package/dist/strategy/__tests__/strategy-mutator.test.js.map +1 -0
  143. package/dist/strategy/index.d.ts +4 -0
  144. package/dist/strategy/index.js +3 -0
  145. package/dist/strategy/index.js.map +1 -0
  146. package/dist/strategy/strategy-forge.d.ts +114 -0
  147. package/dist/strategy/strategy-forge.js +296 -0
  148. package/dist/strategy/strategy-forge.js.map +1 -0
  149. package/dist/strategy/strategy-mutator.d.ts +42 -0
  150. package/dist/strategy/strategy-mutator.js +140 -0
  151. package/dist/strategy/strategy-mutator.js.map +1 -0
  152. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,182 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import Database from 'better-sqlite3';
3
+ vi.mock('../../utils/logger.js', () => ({
4
+ getLogger: () => ({ info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }),
5
+ }));
6
+ import { CodeSandbox, runSandboxMigration } from '../code-sandbox.js';
7
+ describe('CodeSandbox', () => {
8
+ let db;
9
+ beforeEach(() => { db = new Database(':memory:'); });
10
+ afterEach(() => { db.close(); });
11
+ // ── Creation ──────────────────────────────────────────
12
+ it('creates an instance with default config', () => {
13
+ const sandbox = new CodeSandbox(db);
14
+ expect(sandbox).toBeDefined();
15
+ });
16
+ it('creates an instance with custom config', () => {
17
+ const sandbox = new CodeSandbox(db, {
18
+ preferDocker: false,
19
+ defaultTimeoutMs: 5000,
20
+ defaultMemoryMb: 64,
21
+ maxOutputSize: 50000,
22
+ });
23
+ expect(sandbox).toBeDefined();
24
+ });
25
+ // ── getStatus (initial) ───────────────────────────────
26
+ it('returns empty status on fresh instance', () => {
27
+ const sandbox = new CodeSandbox(db);
28
+ const status = sandbox.getStatus();
29
+ expect(status.totalExecutions).toBe(0);
30
+ expect(status.successCount).toBe(0);
31
+ expect(status.failCount).toBe(0);
32
+ expect(status.timeoutCount).toBe(0);
33
+ expect(status.avgDurationMs).toBe(0);
34
+ expect(status.dockerAvailable).toBe(false);
35
+ expect(status.languages).toEqual([]);
36
+ });
37
+ // ── validate ──────────────────────────────────────────
38
+ it('validates valid JavaScript code', () => {
39
+ const sandbox = new CodeSandbox(db);
40
+ const result = sandbox.validate('console.log("hello")', 'javascript');
41
+ expect(result).toBeNull();
42
+ });
43
+ it('validates valid TypeScript code (parsed as JS by Function constructor)', () => {
44
+ const sandbox = new CodeSandbox(db);
45
+ // The validate method uses `new Function(code)` for TS too,
46
+ // so basic JS-compatible TS code passes.
47
+ const result = sandbox.validate('const x = 42; console.log(x);', 'typescript');
48
+ expect(result).toBeNull();
49
+ });
50
+ it('returns error for invalid JavaScript code', () => {
51
+ const sandbox = new CodeSandbox(db);
52
+ const result = sandbox.validate('function {{{', 'javascript');
53
+ expect(result).toBeTruthy();
54
+ expect(typeof result).toBe('string');
55
+ });
56
+ it('returns error for invalid TypeScript code', () => {
57
+ const sandbox = new CodeSandbox(db);
58
+ const result = sandbox.validate('if (', 'typescript');
59
+ expect(result).toBeTruthy();
60
+ });
61
+ it('validates valid Python code', () => {
62
+ const sandbox = new CodeSandbox(db);
63
+ const result = sandbox.validate('print("hello")', 'python');
64
+ expect(result).toBeNull();
65
+ });
66
+ it('returns error for Python def without colon', () => {
67
+ const sandbox = new CodeSandbox(db);
68
+ const result = sandbox.validate('def foo()', 'python');
69
+ expect(result).toBe('Missing colon after def');
70
+ });
71
+ it('validates shell code (always passes)', () => {
72
+ const sandbox = new CodeSandbox(db);
73
+ const result = sandbox.validate('echo hello', 'shell');
74
+ expect(result).toBeNull();
75
+ });
76
+ it('returns error for unsupported language', () => {
77
+ const sandbox = new CodeSandbox(db);
78
+ const result = sandbox.validate('code', 'rust');
79
+ expect(result).toBe('Unsupported language: rust');
80
+ });
81
+ // ── execute ───────────────────────────────────────────
82
+ it('executes simple JavaScript code locally', async () => {
83
+ const sandbox = new CodeSandbox(db, { preferDocker: false });
84
+ const result = await sandbox.execute({
85
+ code: 'console.log("hello")',
86
+ language: 'javascript',
87
+ timeoutMs: 10000,
88
+ name: 'test-exec',
89
+ });
90
+ expect(result.id).toMatch(/^exec-/);
91
+ expect(result.language).toBe('javascript');
92
+ expect(result.stdout.trim()).toBe('hello');
93
+ expect(result.exitCode).toBe(0);
94
+ expect(result.timedOut).toBe(false);
95
+ expect(result.durationMs).toBeGreaterThanOrEqual(0);
96
+ expect(result.name).toBe('test-exec');
97
+ expect(result.createdAt).toBeGreaterThan(0);
98
+ }, 15000);
99
+ it('captures non-zero exit code on failing code', async () => {
100
+ const sandbox = new CodeSandbox(db, { preferDocker: false });
101
+ const result = await sandbox.execute({
102
+ code: 'process.exit(42)',
103
+ language: 'javascript',
104
+ timeoutMs: 10000,
105
+ });
106
+ expect(result.exitCode).not.toBe(0);
107
+ }, 15000);
108
+ // ── getHistory ────────────────────────────────────────
109
+ it('returns execution history after running code', async () => {
110
+ const sandbox = new CodeSandbox(db, { preferDocker: false });
111
+ await sandbox.execute({
112
+ code: 'console.log("history-test")',
113
+ language: 'javascript',
114
+ timeoutMs: 10000,
115
+ name: 'hist-entry',
116
+ });
117
+ const history = sandbox.getHistory(10);
118
+ expect(history).toHaveLength(1);
119
+ expect(history[0].language).toBe('javascript');
120
+ expect(history[0].exitCode).toBe(0);
121
+ expect(history[0].name).toBe('hist-entry');
122
+ expect(history[0].timedOut).toBe(false);
123
+ }, 15000);
124
+ // ── getLanguageStats ──────────────────────────────────
125
+ it('returns language stats after executions', async () => {
126
+ const sandbox = new CodeSandbox(db, { preferDocker: false });
127
+ await sandbox.execute({ code: 'console.log(1)', language: 'javascript', timeoutMs: 10000 });
128
+ await sandbox.execute({ code: 'console.log(2)', language: 'javascript', timeoutMs: 10000 });
129
+ const stats = sandbox.getLanguageStats();
130
+ expect(stats).toHaveLength(1);
131
+ expect(stats[0].language).toBe('javascript');
132
+ expect(stats[0].total).toBe(2);
133
+ expect(stats[0].successRate).toBe(1);
134
+ expect(stats[0].avgDuration).toBeGreaterThanOrEqual(0);
135
+ }, 20000);
136
+ it('returns empty language stats on fresh instance', () => {
137
+ const sandbox = new CodeSandbox(db);
138
+ const stats = sandbox.getLanguageStats();
139
+ expect(stats).toEqual([]);
140
+ });
141
+ // ── isDockerAvailable ─────────────────────────────────
142
+ it('returns boolean from isDockerAvailable (likely false in test env)', async () => {
143
+ const sandbox = new CodeSandbox(db);
144
+ const available = await sandbox.isDockerAvailable();
145
+ expect(typeof available).toBe('boolean');
146
+ }, 10000);
147
+ it('caches Docker availability on subsequent calls', async () => {
148
+ const sandbox = new CodeSandbox(db);
149
+ const first = await sandbox.isDockerAvailable();
150
+ const second = await sandbox.isDockerAvailable();
151
+ expect(first).toBe(second);
152
+ }, 10000);
153
+ it('resets Docker cache', async () => {
154
+ const sandbox = new CodeSandbox(db);
155
+ await sandbox.isDockerAvailable();
156
+ sandbox.resetDockerCache();
157
+ // After reset, the next call re-checks (we just verify it does not throw)
158
+ const result = await sandbox.isDockerAvailable();
159
+ expect(typeof result).toBe('boolean');
160
+ }, 10000);
161
+ // ── Status after executions ───────────────────────────
162
+ it('reflects executions in status', async () => {
163
+ const sandbox = new CodeSandbox(db, { preferDocker: false });
164
+ await sandbox.execute({ code: 'console.log("ok")', language: 'javascript', timeoutMs: 10000 });
165
+ const status = sandbox.getStatus();
166
+ expect(status.totalExecutions).toBe(1);
167
+ expect(status.successCount).toBe(1);
168
+ expect(status.failCount).toBe(0);
169
+ expect(status.timeoutCount).toBe(0);
170
+ expect(status.languages).toContain('javascript');
171
+ expect(status.avgDurationMs).toBeGreaterThanOrEqual(0);
172
+ }, 15000);
173
+ // ── Migration idempotency ─────────────────────────────
174
+ it('runs migration multiple times without error', () => {
175
+ runSandboxMigration(db);
176
+ runSandboxMigration(db);
177
+ // Table already exists from constructor, third call is fine too
178
+ const sandbox = new CodeSandbox(db);
179
+ expect(sandbox.getStatus().totalExecutions).toBe(0);
180
+ });
181
+ });
182
+ //# sourceMappingURL=code-sandbox.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-sandbox.test.js","sourceRoot":"","sources":["../../../src/sandbox/__tests__/code-sandbox.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,142 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import Database from 'better-sqlite3';
3
+ import { SignalScanner, runScannerMigration } from '../signal-scanner.js';
4
+ vi.mock('../../utils/logger.js', () => ({
5
+ getLogger: () => ({
6
+ info: vi.fn(),
7
+ warn: vi.fn(),
8
+ error: vi.fn(),
9
+ debug: vi.fn(),
10
+ }),
11
+ }));
12
+ vi.mock('../github-collector.js', () => ({
13
+ GitHubCollector: vi.fn().mockImplementation(() => ({
14
+ abort: vi.fn(),
15
+ reset: vi.fn(),
16
+ collectEmerging: vi.fn().mockResolvedValue([]),
17
+ collectTrending: vi.fn().mockResolvedValue([]),
18
+ })),
19
+ }));
20
+ vi.mock('../hn-collector.js', () => ({
21
+ HnCollector: vi.fn().mockImplementation(() => ({
22
+ abort: vi.fn(),
23
+ reset: vi.fn(),
24
+ collectFrontpage: vi.fn().mockResolvedValue([]),
25
+ })),
26
+ }));
27
+ vi.mock('../crypto-collector.js', () => ({
28
+ CryptoCollector: vi.fn().mockImplementation(() => ({
29
+ abort: vi.fn(),
30
+ reset: vi.fn(),
31
+ collectWatchlist: vi.fn().mockResolvedValue([]),
32
+ collectTrending: vi.fn().mockResolvedValue(null),
33
+ })),
34
+ }));
35
+ vi.mock('../signal-scorer.js', () => ({
36
+ scoreRepo: vi.fn().mockReturnValue({ total: 0, level: 'noise', phase: 'discovery' }),
37
+ classifyWithHysteresis: vi.fn().mockReturnValue({ level: 'noise', peak: null, peakSince: null }),
38
+ scoreCrypto: vi.fn().mockReturnValue({ score: 0, level: 'noise' }),
39
+ }));
40
+ describe('SignalScanner', () => {
41
+ let db;
42
+ let scanner;
43
+ beforeEach(() => {
44
+ db = new Database(':memory:');
45
+ runScannerMigration(db);
46
+ scanner = new SignalScanner(db, { enabled: true, githubToken: 'fake-token' });
47
+ });
48
+ afterEach(() => {
49
+ scanner.stop();
50
+ db.close();
51
+ });
52
+ // ── 1. Creation ──────────────────────────────────────────────
53
+ it('should create a SignalScanner instance', () => {
54
+ expect(scanner).toBeInstanceOf(SignalScanner);
55
+ });
56
+ // ── 2. getStatus (initial) ───────────────────────────────────
57
+ it('should return initial status with zero counts', () => {
58
+ const status = scanner.getStatus();
59
+ expect(status.running).toBe(false);
60
+ expect(status.enabled).toBe(true);
61
+ expect(status.last_scan).toBeNull();
62
+ expect(status.total_repos).toBe(0);
63
+ expect(status.total_active).toBe(0);
64
+ expect(status.by_level).toEqual({ breakout: 0, signal: 0, watch: 0, noise: 0 });
65
+ expect(status.next_scan_at).toBeNull();
66
+ });
67
+ // ── 3. getConfig ─────────────────────────────────────────────
68
+ it('should return config with defaults merged', () => {
69
+ const config = scanner.getConfig();
70
+ expect(config.enabled).toBe(true);
71
+ expect(config.githubToken).toBe('fake-token');
72
+ expect(config.scanIntervalMs).toBe(21_600_000);
73
+ expect(config.minStarsEmerging).toBe(15);
74
+ expect(config.minStarsTrending).toBe(200);
75
+ expect(config.maxReposPerScan).toBe(5000);
76
+ expect(config.cryptoEnabled).toBe(true);
77
+ expect(config.hnEnabled).toBe(true);
78
+ });
79
+ // ── 4. updateConfig ──────────────────────────────────────────
80
+ it('should update config and return the merged result', () => {
81
+ const updated = scanner.updateConfig({ minStarsEmerging: 50, cryptoEnabled: false });
82
+ expect(updated.minStarsEmerging).toBe(50);
83
+ expect(updated.cryptoEnabled).toBe(false);
84
+ // Unchanged fields remain
85
+ expect(updated.githubToken).toBe('fake-token');
86
+ expect(updated.scanIntervalMs).toBe(21_600_000);
87
+ // Verify via getConfig too
88
+ const config = scanner.getConfig();
89
+ expect(config.minStarsEmerging).toBe(50);
90
+ expect(config.cryptoEnabled).toBe(false);
91
+ });
92
+ // ── 5. getSignals (empty) ────────────────────────────────────
93
+ it('should return empty array for getSignals when no repos exist', () => {
94
+ const breakouts = scanner.getSignals('breakout');
95
+ const signals = scanner.getSignals('signal');
96
+ const watches = scanner.getSignals('watch');
97
+ const noise = scanner.getSignals('noise');
98
+ expect(breakouts).toEqual([]);
99
+ expect(signals).toEqual([]);
100
+ expect(watches).toEqual([]);
101
+ expect(noise).toEqual([]);
102
+ });
103
+ // ── 6. getTrending (empty) ───────────────────────────────────
104
+ it('should return empty array for getTrending when no repos exist', () => {
105
+ const trending = scanner.getTrending();
106
+ expect(trending).toEqual([]);
107
+ });
108
+ // ── 7. searchRepos (empty) ───────────────────────────────────
109
+ it('should return empty array for searchRepos when no repos exist', () => {
110
+ const results = scanner.searchRepos('typescript');
111
+ expect(results).toEqual([]);
112
+ const withLang = scanner.searchRepos('', 'TypeScript');
113
+ expect(withLang).toEqual([]);
114
+ });
115
+ // ── 8. getStats ──────────────────────────────────────────────
116
+ it('should return initial stats with all zeroes', () => {
117
+ const stats = scanner.getStats();
118
+ expect(stats.total_repos).toBe(0);
119
+ expect(stats.active_repos).toBe(0);
120
+ expect(stats.by_language).toEqual([]);
121
+ expect(stats.by_level).toEqual([]);
122
+ expect(stats.hn_mentions).toBe(0);
123
+ expect(stats.crypto_tokens).toBe(0);
124
+ expect(stats.avg_score).toBe(0);
125
+ expect(stats.last_scan).toBeNull();
126
+ });
127
+ // ── 9. getHnMentions (empty) ─────────────────────────────────
128
+ it('should return empty array for getHnMentions when no mentions exist', () => {
129
+ const mentions = scanner.getHnMentions();
130
+ expect(mentions).toEqual([]);
131
+ const limited = scanner.getHnMentions(10);
132
+ expect(limited).toEqual([]);
133
+ });
134
+ // ── 10. getCryptoTokens (empty) ──────────────────────────────
135
+ it('should return empty array for getCryptoTokens when no tokens exist', () => {
136
+ const tokens = scanner.getCryptoTokens();
137
+ expect(tokens).toEqual([]);
138
+ const limited = scanner.getCryptoTokens(10);
139
+ expect(limited).toEqual([]);
140
+ });
141
+ });
142
+ //# sourceMappingURL=signal-scanner.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signal-scanner.test.js","sourceRoot":"","sources":["../../../src/scanner/__tests__/signal-scanner.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,255 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import Database from 'better-sqlite3';
3
+ import { SelfModificationEngine, runSelfModificationMigration, } from '../self-modification-engine.js';
4
+ vi.mock('../../utils/logger.js', () => ({
5
+ getLogger: () => ({
6
+ info: vi.fn(),
7
+ warn: vi.fn(),
8
+ error: vi.fn(),
9
+ debug: vi.fn(),
10
+ }),
11
+ }));
12
+ describe('SelfModificationEngine', () => {
13
+ let db;
14
+ let engine;
15
+ beforeEach(() => {
16
+ db = new Database(':memory:');
17
+ runSelfModificationMigration(db);
18
+ engine = new SelfModificationEngine(db, {
19
+ brainName: 'test-brain',
20
+ maxPerHour: 10,
21
+ maxChangedLines: 200,
22
+ });
23
+ });
24
+ afterEach(() => {
25
+ db.close();
26
+ });
27
+ // ── 1. Creation ──────────────────────────────────────────
28
+ it('should create the self_modifications table on construction', () => {
29
+ const tables = db
30
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='self_modifications'")
31
+ .all();
32
+ expect(tables).toHaveLength(1);
33
+ expect(tables[0].name).toBe('self_modifications');
34
+ });
35
+ it('should include experiment ledger columns after migration', () => {
36
+ const columns = db.pragma('table_info(self_modifications)');
37
+ const colNames = columns.map(c => c.name);
38
+ expect(colNames).toContain('hypothesis');
39
+ expect(colNames).toContain('risk_level');
40
+ expect(colNames).toContain('expected_impact');
41
+ expect(colNames).toContain('acceptance_criteria');
42
+ expect(colNames).toContain('reason_code');
43
+ expect(colNames).toContain('metrics_before');
44
+ expect(colNames).toContain('metrics_after');
45
+ });
46
+ // ── 2. getStatus (initial) ───────────────────────────────
47
+ it('should return correct initial status with zero modifications', () => {
48
+ const status = engine.getStatus();
49
+ expect(status.brainName).toBe('test-brain');
50
+ expect(status.totalModifications).toBe(0);
51
+ expect(status.byStatus).toEqual({});
52
+ expect(status.lastModification).toBeNull();
53
+ expect(status.projectRoot).toBeNull();
54
+ });
55
+ // ── 3. propose (creates a modification) ──────────────────
56
+ it('should create a proposed modification with correct fields', () => {
57
+ const mod = engine.proposeModification('Refactor utils', 'Extract shared helpers into a utility module', ['packages/brain-core/src/utils/helpers.ts'], 'orchestrator');
58
+ expect(mod.id).toBe(1);
59
+ expect(mod.title).toBe('Refactor utils');
60
+ expect(mod.problem_description).toBe('Extract shared helpers into a utility module');
61
+ expect(mod.source_engine).toBe('orchestrator');
62
+ expect(mod.target_files).toEqual(['packages/brain-core/src/utils/helpers.ts']);
63
+ expect(mod.status).toBe('proposed');
64
+ expect(mod.test_result).toBe('pending');
65
+ expect(mod.generated_diff).toBeNull();
66
+ expect(mod.applied_at).toBeNull();
67
+ expect(mod.rollback_data).toBeNull();
68
+ expect(mod.tokens_used).toBe(0);
69
+ expect(mod.created_at).toBeTruthy();
70
+ });
71
+ it('should store proposal metadata when provided', () => {
72
+ const meta = {
73
+ hypothesis: 'Extracting helpers will reduce duplication by 20%',
74
+ risk_level: 'low',
75
+ expected_impact: [
76
+ { metric: 'loc_duplicated', direction: 'decrease', target: '-20%' },
77
+ ],
78
+ acceptance_criteria: ['Build passes', 'All tests green'],
79
+ };
80
+ const mod = engine.proposeModification('Deduplicate helpers', 'Reduce duplicated code', ['packages/brain-core/src/utils/shared.ts'], 'orchestrator', meta);
81
+ expect(mod.hypothesis).toBe('Extracting helpers will reduce duplication by 20%');
82
+ expect(mod.risk_level).toBe('low');
83
+ expect(mod.expected_impact).toEqual([
84
+ { metric: 'loc_duplicated', direction: 'decrease', target: '-20%' },
85
+ ]);
86
+ expect(mod.acceptance_criteria).toEqual(['Build passes', 'All tests green']);
87
+ });
88
+ it('should reject paths outside whitelist (node_modules)', () => {
89
+ expect(() => engine.proposeModification('Bad', 'Problem', ['node_modules/foo/bar.ts'])).toThrow('Path not allowed');
90
+ });
91
+ it('should reject test file paths', () => {
92
+ expect(() => engine.proposeModification('Bad', 'Problem', ['packages/brain-core/src/engine.test.ts'])).toThrow('Path not allowed');
93
+ });
94
+ it('should reject protected core paths (guardrails)', () => {
95
+ expect(() => engine.proposeModification('Bad', 'Problem', ['packages/brain-core/src/guardrails/engine.ts'])).toThrow('Path not allowed');
96
+ });
97
+ // ── 4. getModification (by id) ───────────────────────────
98
+ it('should retrieve a modification by id', () => {
99
+ engine.proposeModification('First', 'Problem 1', ['packages/brain-core/src/a.ts']);
100
+ engine.proposeModification('Second', 'Problem 2', ['packages/brain-core/src/b.ts']);
101
+ const mod = engine.getModification(2);
102
+ expect(mod).not.toBeNull();
103
+ expect(mod.title).toBe('Second');
104
+ expect(mod.id).toBe(2);
105
+ });
106
+ it('should return null for a non-existent id', () => {
107
+ const mod = engine.getModification(999);
108
+ expect(mod).toBeNull();
109
+ });
110
+ // ── 5. getPending ────────────────────────────────────────
111
+ it('should return all proposed and ready modifications', () => {
112
+ engine.proposeModification('A', 'P-A', ['packages/brain-core/src/a.ts']);
113
+ engine.proposeModification('B', 'P-B', ['packages/brain-core/src/b.ts']);
114
+ engine.proposeModification('C', 'P-C', ['packages/brain-core/src/c.ts']);
115
+ // Reject one so it should NOT appear in pending
116
+ engine.rejectModification(2);
117
+ const pending = engine.getPending();
118
+ expect(pending).toHaveLength(2);
119
+ const titles = pending.map(p => p.title);
120
+ expect(titles).toContain('A');
121
+ expect(titles).toContain('C');
122
+ expect(titles).not.toContain('B');
123
+ });
124
+ it('should return pending in descending id order (newest first)', () => {
125
+ engine.proposeModification('First', 'P1', ['packages/brain-core/src/x.ts']);
126
+ engine.proposeModification('Second', 'P2', ['packages/brain-core/src/y.ts']);
127
+ const pending = engine.getPending();
128
+ expect(pending[0].title).toBe('Second');
129
+ expect(pending[1].title).toBe('First');
130
+ });
131
+ // ── 6. approve / reject ─────────────────────────────────
132
+ it('should reject a modification with notes and reason code', () => {
133
+ engine.proposeModification('Reject me', 'Problem', ['packages/brain-core/src/foo.ts']);
134
+ const rejected = engine.rejectModification(1, 'Breaks public API', 'API_BREAK');
135
+ expect(rejected.status).toBe('rejected');
136
+ expect(rejected.test_output).toContain('Breaks public API');
137
+ expect(rejected.reason_code).toBe('API_BREAK');
138
+ });
139
+ it('should reject without notes (keeps existing test_output)', () => {
140
+ engine.proposeModification('Reject silent', 'Problem', ['packages/brain-core/src/foo.ts']);
141
+ const rejected = engine.rejectModification(1);
142
+ expect(rejected.status).toBe('rejected');
143
+ // test_output stays null since no notes were provided and no prior output existed
144
+ expect(rejected.test_output).toBeNull();
145
+ });
146
+ it('should throw when approving a non-ready modification', () => {
147
+ engine.proposeModification('Not ready', 'Problem', ['packages/brain-core/src/foo.ts']);
148
+ expect(() => engine.approveModification(1)).toThrow('not ready');
149
+ });
150
+ it('should throw when approving a non-existent modification', () => {
151
+ expect(() => engine.approveModification(999)).toThrow('not found');
152
+ });
153
+ // ── 7. getHistory ────────────────────────────────────────
154
+ it('should return history limited to requested count', () => {
155
+ engine.proposeModification('H1', 'P', ['packages/brain-core/src/a.ts']);
156
+ engine.proposeModification('H2', 'P', ['packages/brain-core/src/b.ts']);
157
+ engine.proposeModification('H3', 'P', ['packages/brain-core/src/c.ts']);
158
+ engine.proposeModification('H4', 'P', ['packages/brain-core/src/d.ts']);
159
+ const history = engine.getHistory(2);
160
+ expect(history).toHaveLength(2);
161
+ // Most recent first
162
+ expect(history[0].title).toBe('H4');
163
+ expect(history[1].title).toBe('H3');
164
+ });
165
+ it('should return all modifications when limit exceeds count', () => {
166
+ engine.proposeModification('Only', 'P', ['packages/brain-core/src/a.ts']);
167
+ const history = engine.getHistory(50);
168
+ expect(history).toHaveLength(1);
169
+ });
170
+ // ── 8. recordMetrics ─────────────────────────────────────
171
+ it('should record before and after metrics for experiment tracking', () => {
172
+ engine.proposeModification('Metrics', 'Measure impact', ['packages/brain-core/src/foo.ts']);
173
+ engine.recordMetrics(1, 'before', { test_count: 200, coverage: 75 });
174
+ engine.recordMetrics(1, 'after', { test_count: 210, coverage: 78 });
175
+ const mod = engine.getModification(1);
176
+ expect(mod.metrics_before).toEqual({ test_count: 200, coverage: 75 });
177
+ expect(mod.metrics_after).toEqual({ test_count: 210, coverage: 78 });
178
+ });
179
+ // ── 9. Status counts after mixed operations ──────────────
180
+ it('should track status counts correctly across operations', () => {
181
+ engine.proposeModification('S1', 'P', ['packages/brain-core/src/a.ts']);
182
+ engine.proposeModification('S2', 'P', ['packages/brain-core/src/b.ts']);
183
+ engine.proposeModification('S3', 'P', ['packages/brain-core/src/c.ts']);
184
+ engine.rejectModification(1);
185
+ engine.rejectModification(2, 'Not needed', 'NOT_NEEDED');
186
+ const status = engine.getStatus();
187
+ expect(status.totalModifications).toBe(3);
188
+ expect(status.byStatus.proposed).toBe(1);
189
+ expect(status.byStatus.rejected).toBe(2);
190
+ expect(status.lastModification).toBeTruthy();
191
+ });
192
+ // ── 10. Crash recovery ──────────────────────────────────
193
+ it('should recover modifications stuck in testing state on construction', () => {
194
+ // Manually insert a row stuck in "testing" status (simulates a crash)
195
+ db.prepare(`
196
+ INSERT INTO self_modifications (title, problem_description, target_files, status, rollback_data)
197
+ VALUES ('Crashed mod', 'was mid-test', '[]', 'testing', '[]')
198
+ `).run();
199
+ // Creating a new engine instance triggers crash recovery
200
+ const engine2 = new SelfModificationEngine(db, { brainName: 'recovery-brain' });
201
+ const recovered = engine2.getModification(1);
202
+ expect(recovered).not.toBeNull();
203
+ expect(recovered.status).toBe('failed');
204
+ expect(recovered.test_output).toContain('crash');
205
+ });
206
+ // ── 11. generateCode guards ──────────────────────────────
207
+ it('should throw when generating code for non-existent modification', async () => {
208
+ await expect(engine.generateCode(999)).rejects.toThrow('not found');
209
+ });
210
+ it('should throw when generating code for a rejected modification', async () => {
211
+ engine.proposeModification('Rejected', 'Problem', ['packages/brain-core/src/foo.ts']);
212
+ engine.rejectModification(1);
213
+ await expect(engine.generateCode(1)).rejects.toThrow("not in 'proposed' state");
214
+ });
215
+ // ── 12. rollback guards ──────────────────────────────────
216
+ it('should throw when rolling back a non-applied modification', () => {
217
+ engine.proposeModification('Not applied', 'Problem', ['packages/brain-core/src/foo.ts']);
218
+ expect(() => engine.rollbackModification(1)).toThrow('not applied');
219
+ });
220
+ it('should throw when rolling back a non-existent modification', () => {
221
+ expect(() => engine.rollbackModification(999)).toThrow('not found');
222
+ });
223
+ // ── 13. testModification guard ───────────────────────────
224
+ it('should throw when testing without projectRoot configured', () => {
225
+ engine.proposeModification('No root', 'Problem', ['packages/brain-core/src/foo.ts']);
226
+ db.prepare(`UPDATE self_modifications SET generated_diff = ? WHERE id = 1`).run(JSON.stringify([
227
+ {
228
+ filePath: 'packages/brain-core/src/foo.ts',
229
+ oldContent: 'const a = 1;',
230
+ newContent: 'const a = 2;',
231
+ },
232
+ ]));
233
+ expect(() => engine.testModification(1)).toThrow('projectRoot');
234
+ });
235
+ it('should throw when testing a modification with no generated code', () => {
236
+ engine.proposeModification('No code', 'Problem', ['packages/brain-core/src/foo.ts']);
237
+ // generated_diff is null by default
238
+ expect(() => engine.testModification(1)).toThrow('no generated code');
239
+ });
240
+ // ── 14. Multiple target files ────────────────────────────
241
+ it('should handle multiple target files in a single proposal', () => {
242
+ const mod = engine.proposeModification('Multi-file', 'Refactor across files', [
243
+ 'packages/brain-core/src/alpha.ts',
244
+ 'packages/brain-core/src/beta.ts',
245
+ 'packages/brain-core/src/gamma.ts',
246
+ ]);
247
+ expect(mod.target_files).toHaveLength(3);
248
+ expect(mod.target_files).toEqual([
249
+ 'packages/brain-core/src/alpha.ts',
250
+ 'packages/brain-core/src/beta.ts',
251
+ 'packages/brain-core/src/gamma.ts',
252
+ ]);
253
+ });
254
+ });
255
+ //# sourceMappingURL=self-modification-engine.test.js.map