@softerist/heuristic-mcp 2.1.46 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/workflows/code-review.md +60 -0
- package/.prettierrc +7 -0
- package/ARCHITECTURE.md +105 -170
- package/CONTRIBUTING.md +32 -113
- package/GEMINI.md +73 -0
- package/LICENSE +21 -21
- package/README.md +161 -54
- package/config.json +876 -76
- package/debug-pids.js +27 -0
- package/eslint.config.js +36 -0
- package/features/ann-config.js +37 -26
- package/features/clear-cache.js +28 -19
- package/features/find-similar-code.js +142 -66
- package/features/hybrid-search.js +253 -93
- package/features/index-codebase.js +1455 -394
- package/features/lifecycle.js +813 -180
- package/features/register.js +58 -52
- package/index.js +450 -306
- package/lib/cache-ops.js +22 -0
- package/lib/cache-utils.js +68 -0
- package/lib/cache.js +1392 -587
- package/lib/call-graph.js +165 -50
- package/lib/cli.js +154 -0
- package/lib/config.js +462 -121
- package/lib/embedding-process.js +77 -0
- package/lib/embedding-worker.js +545 -30
- package/lib/ignore-patterns.js +61 -59
- package/lib/json-worker.js +14 -0
- package/lib/json-writer.js +344 -0
- package/lib/logging.js +88 -0
- package/lib/memory-logger.js +13 -0
- package/lib/project-detector.js +13 -17
- package/lib/server-lifecycle.js +38 -0
- package/lib/settings-editor.js +645 -0
- package/lib/tokenizer.js +207 -104
- package/lib/utils.js +273 -198
- package/lib/vector-store-binary.js +592 -0
- package/mcp_config.example.json +13 -0
- package/package.json +13 -2
- package/scripts/clear-cache.js +6 -17
- package/scripts/download-model.js +14 -9
- package/scripts/postinstall.js +5 -5
- package/search-configs.js +36 -0
- package/test/ann-config.test.js +179 -0
- package/test/ann-fallback.test.js +6 -6
- package/test/binary-store.test.js +69 -0
- package/test/cache-branches.test.js +120 -0
- package/test/cache-errors.test.js +264 -0
- package/test/cache-extra.test.js +300 -0
- package/test/cache-helpers.test.js +205 -0
- package/test/cache-hnsw-failure.test.js +40 -0
- package/test/cache-json-worker.test.js +190 -0
- package/test/cache-worker.test.js +102 -0
- package/test/cache.test.js +443 -0
- package/test/call-graph.test.js +103 -4
- package/test/clear-cache.test.js +69 -68
- package/test/code-review-workflow.test.js +50 -0
- package/test/config.test.js +418 -0
- package/test/coverage-gap.test.js +497 -0
- package/test/coverage-maximizer.test.js +236 -0
- package/test/debug-analysis.js +107 -0
- package/test/embedding-model.test.js +173 -103
- package/test/embedding-worker-extra.test.js +272 -0
- package/test/embedding-worker.test.js +158 -0
- package/test/features.test.js +139 -0
- package/test/final-boost.test.js +271 -0
- package/test/final-polish.test.js +183 -0
- package/test/final.test.js +95 -0
- package/test/find-similar-code.test.js +191 -0
- package/test/helpers.js +92 -11
- package/test/helpers.test.js +46 -0
- package/test/hybrid-search-basic.test.js +62 -0
- package/test/hybrid-search-branch.test.js +202 -0
- package/test/hybrid-search-callgraph.test.js +229 -0
- package/test/hybrid-search-extra.test.js +81 -0
- package/test/hybrid-search.test.js +484 -71
- package/test/index-cli.test.js +520 -0
- package/test/index-codebase-batch.test.js +119 -0
- package/test/index-codebase-branches.test.js +585 -0
- package/test/index-codebase-core.test.js +1032 -0
- package/test/index-codebase-edge-cases.test.js +254 -0
- package/test/index-codebase-errors.test.js +132 -0
- package/test/index-codebase-gap.test.js +239 -0
- package/test/index-codebase-lines.test.js +151 -0
- package/test/index-codebase-watcher.test.js +259 -0
- package/test/index-codebase-zone.test.js +259 -0
- package/test/index-codebase.test.js +371 -69
- package/test/index-memory.test.js +220 -0
- package/test/indexer-detailed.test.js +176 -0
- package/test/integration.test.js +148 -92
- package/test/json-worker.test.js +50 -0
- package/test/lifecycle.test.js +541 -0
- package/test/master.test.js +198 -0
- package/test/perfection.test.js +349 -0
- package/test/project-detector.test.js +65 -0
- package/test/register.test.js +262 -0
- package/test/tokenizer.test.js +55 -93
- package/test/ultra-maximizer.test.js +116 -0
- package/test/utils-branches.test.js +161 -0
- package/test/utils-extra.test.js +116 -0
- package/test/utils.test.js +131 -0
- package/test/verify_fixes.js +76 -0
- package/test/worker-errors.test.js +96 -0
- package/test/worker-init.test.js +102 -0
- package/test/worker_throttling.test.js +93 -0
- package/tools/scripts/benchmark-search.js +95 -0
- package/tools/scripts/cache-stats.js +71 -0
- package/tools/scripts/manual-search.js +34 -0
- package/vitest.config.js +19 -9
package/test/clear-cache.test.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for CacheClearer feature
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Tests the cache clearing functionality including:
|
|
5
5
|
* - Basic cache clearing
|
|
6
6
|
* - Protection during indexing
|
|
@@ -10,28 +10,27 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
|
|
13
|
-
import {
|
|
14
|
-
createTestFixtures,
|
|
15
|
-
cleanupFixtures,
|
|
13
|
+
import {
|
|
14
|
+
createTestFixtures,
|
|
15
|
+
cleanupFixtures,
|
|
16
16
|
clearTestCache,
|
|
17
|
-
createMockRequest
|
|
17
|
+
createMockRequest,
|
|
18
18
|
} from './helpers.js';
|
|
19
19
|
import * as ClearCacheFeature from '../features/clear-cache.js';
|
|
20
20
|
import { CacheClearer } from '../features/clear-cache.js';
|
|
21
21
|
import fs from 'fs/promises';
|
|
22
|
-
import path from 'path';
|
|
23
22
|
|
|
24
23
|
describe('CacheClearer', () => {
|
|
25
24
|
let fixtures;
|
|
26
|
-
|
|
25
|
+
|
|
27
26
|
beforeAll(async () => {
|
|
28
27
|
fixtures = await createTestFixtures({ workerThreads: 1 });
|
|
29
28
|
});
|
|
30
|
-
|
|
29
|
+
|
|
31
30
|
afterAll(async () => {
|
|
32
31
|
await cleanupFixtures(fixtures);
|
|
33
32
|
});
|
|
34
|
-
|
|
33
|
+
|
|
35
34
|
beforeEach(async () => {
|
|
36
35
|
// Reset state
|
|
37
36
|
fixtures.indexer.isIndexing = false;
|
|
@@ -41,42 +40,44 @@ describe('CacheClearer', () => {
|
|
|
41
40
|
|
|
42
41
|
describe('Basic Cache Clearing', () => {
|
|
43
42
|
it('should clear cache successfully', async () => {
|
|
43
|
+
expect(fixtures.cacheClearer).toBeInstanceOf(CacheClearer);
|
|
44
|
+
|
|
44
45
|
// First ensure we have a cache
|
|
45
46
|
await fixtures.indexer.indexAll(true);
|
|
46
|
-
|
|
47
|
+
|
|
47
48
|
// Verify cache exists
|
|
48
49
|
expect(fixtures.cache.getVectorStore().length).toBeGreaterThan(0);
|
|
49
|
-
|
|
50
|
+
|
|
50
51
|
// Clear cache
|
|
51
52
|
const result = await fixtures.cacheClearer.execute();
|
|
52
|
-
|
|
53
|
+
|
|
53
54
|
expect(result.success).toBe(true);
|
|
54
55
|
expect(result.message).toContain('Cache cleared successfully');
|
|
55
56
|
expect(result.cacheDirectory).toBe(fixtures.config.cacheDirectory);
|
|
56
57
|
});
|
|
57
|
-
|
|
58
|
+
|
|
58
59
|
it('should empty vectorStore and fileHashes', async () => {
|
|
59
60
|
// Create some cache
|
|
60
61
|
await fixtures.indexer.indexAll(true);
|
|
61
|
-
|
|
62
|
+
|
|
62
63
|
// Clear
|
|
63
64
|
await fixtures.cacheClearer.execute();
|
|
64
|
-
|
|
65
|
+
|
|
65
66
|
// Both should be empty
|
|
66
67
|
expect(fixtures.cache.getVectorStore().length).toBe(0);
|
|
67
|
-
expect(fixtures.cache.
|
|
68
|
+
expect(fixtures.cache.getFileHashCount()).toBe(0);
|
|
68
69
|
});
|
|
69
|
-
|
|
70
|
+
|
|
70
71
|
it('should delete cache directory', async () => {
|
|
71
72
|
// Create cache
|
|
72
73
|
await fixtures.indexer.indexAll(true);
|
|
73
|
-
|
|
74
|
+
|
|
74
75
|
// Verify cache directory exists
|
|
75
76
|
await expect(fs.access(fixtures.config.cacheDirectory)).resolves.not.toThrow();
|
|
76
|
-
|
|
77
|
+
|
|
77
78
|
// Clear
|
|
78
79
|
await fixtures.cacheClearer.execute();
|
|
79
|
-
|
|
80
|
+
|
|
80
81
|
// Directory should not exist
|
|
81
82
|
await expect(fs.access(fixtures.config.cacheDirectory)).rejects.toThrow();
|
|
82
83
|
});
|
|
@@ -87,24 +88,24 @@ describe('CacheClearer', () => {
|
|
|
87
88
|
// Simulate indexing in progress
|
|
88
89
|
await clearTestCache(fixtures.config);
|
|
89
90
|
fixtures.cache.setVectorStore([]);
|
|
90
|
-
fixtures.cache.
|
|
91
|
-
|
|
91
|
+
fixtures.cache.clearFileHashes();
|
|
92
|
+
|
|
92
93
|
const indexPromise = fixtures.indexer.indexAll(true);
|
|
93
94
|
expect(fixtures.indexer.isIndexing).toBe(true);
|
|
94
|
-
|
|
95
|
+
|
|
95
96
|
// Try to clear - should fail
|
|
96
97
|
await expect(fixtures.cacheClearer.execute()).rejects.toThrow(
|
|
97
98
|
'Cannot clear cache while indexing is in progress'
|
|
98
99
|
);
|
|
99
|
-
|
|
100
|
+
|
|
100
101
|
await indexPromise;
|
|
101
102
|
});
|
|
102
|
-
|
|
103
|
+
|
|
103
104
|
it('should allow clear after indexing completes', async () => {
|
|
104
105
|
// Complete indexing
|
|
105
106
|
await fixtures.indexer.indexAll(true);
|
|
106
107
|
expect(fixtures.indexer.isIndexing).toBe(false);
|
|
107
|
-
|
|
108
|
+
|
|
108
109
|
// Clear should work
|
|
109
110
|
const result = await fixtures.cacheClearer.execute();
|
|
110
111
|
expect(result.success).toBe(true);
|
|
@@ -115,23 +116,23 @@ describe('CacheClearer', () => {
|
|
|
115
116
|
it('should prevent clear while cache is being saved', async () => {
|
|
116
117
|
// Simulate save in progress
|
|
117
118
|
fixtures.cache.isSaving = true;
|
|
118
|
-
|
|
119
|
+
|
|
119
120
|
// Try to clear - should fail
|
|
120
121
|
await expect(fixtures.cacheClearer.execute()).rejects.toThrow(
|
|
121
122
|
'Cannot clear cache while cache is being saved'
|
|
122
123
|
);
|
|
123
|
-
|
|
124
|
+
|
|
124
125
|
// Reset
|
|
125
126
|
fixtures.cache.isSaving = false;
|
|
126
127
|
});
|
|
127
|
-
|
|
128
|
+
|
|
128
129
|
it('should allow clear after save completes', async () => {
|
|
129
130
|
// Index first
|
|
130
131
|
await fixtures.indexer.indexAll(true);
|
|
131
|
-
|
|
132
|
+
|
|
132
133
|
// isSaving should be false after indexing
|
|
133
134
|
expect(fixtures.cache.isSaving).toBe(false);
|
|
134
|
-
|
|
135
|
+
|
|
135
136
|
// Clear should work
|
|
136
137
|
const result = await fixtures.cacheClearer.execute();
|
|
137
138
|
expect(result.success).toBe(true);
|
|
@@ -142,58 +143,58 @@ describe('CacheClearer', () => {
|
|
|
142
143
|
it('should prevent multiple concurrent clears', async () => {
|
|
143
144
|
// Index first
|
|
144
145
|
await fixtures.indexer.indexAll(true);
|
|
145
|
-
|
|
146
|
+
|
|
146
147
|
// Reset the isClearing flag
|
|
147
148
|
fixtures.cacheClearer.isClearing = false;
|
|
148
|
-
|
|
149
|
+
|
|
149
150
|
// Start multiple concurrent clears
|
|
150
151
|
const promises = [
|
|
151
152
|
fixtures.cacheClearer.execute(),
|
|
152
153
|
fixtures.cacheClearer.execute(),
|
|
153
|
-
fixtures.cacheClearer.execute()
|
|
154
|
+
fixtures.cacheClearer.execute(),
|
|
154
155
|
];
|
|
155
|
-
|
|
156
|
+
|
|
156
157
|
const results = await Promise.allSettled(promises);
|
|
157
|
-
|
|
158
|
+
|
|
158
159
|
// Exactly one should succeed
|
|
159
|
-
const successes = results.filter(r => r.status === 'fulfilled');
|
|
160
|
-
const failures = results.filter(r => r.status === 'rejected');
|
|
161
|
-
|
|
160
|
+
const successes = results.filter((r) => r.status === 'fulfilled');
|
|
161
|
+
const failures = results.filter((r) => r.status === 'rejected');
|
|
162
|
+
|
|
162
163
|
expect(successes.length).toBe(1);
|
|
163
164
|
expect(failures.length).toBe(2);
|
|
164
|
-
|
|
165
|
+
|
|
165
166
|
// Failures should have correct error message
|
|
166
167
|
for (const failure of failures) {
|
|
167
168
|
expect(failure.reason.message).toContain('already in progress');
|
|
168
169
|
}
|
|
169
170
|
});
|
|
170
|
-
|
|
171
|
+
|
|
171
172
|
it('should reset isClearing flag after completion', async () => {
|
|
172
173
|
// Index first
|
|
173
174
|
await fixtures.indexer.indexAll(true);
|
|
174
|
-
|
|
175
|
+
|
|
175
176
|
expect(fixtures.cacheClearer.isClearing).toBe(false);
|
|
176
|
-
|
|
177
|
+
|
|
177
178
|
// Clear
|
|
178
179
|
await fixtures.cacheClearer.execute();
|
|
179
|
-
|
|
180
|
+
|
|
180
181
|
// Flag should be reset
|
|
181
182
|
expect(fixtures.cacheClearer.isClearing).toBe(false);
|
|
182
183
|
});
|
|
183
|
-
|
|
184
|
+
|
|
184
185
|
it('should reset isClearing flag even on error', async () => {
|
|
185
186
|
// Set up for failure
|
|
186
187
|
fixtures.cache.isSaving = true;
|
|
187
|
-
|
|
188
|
+
|
|
188
189
|
try {
|
|
189
190
|
await fixtures.cacheClearer.execute();
|
|
190
191
|
} catch {
|
|
191
192
|
// Expected to fail
|
|
192
193
|
}
|
|
193
|
-
|
|
194
|
+
|
|
194
195
|
// isClearing should not have been set (failed before setting)
|
|
195
196
|
expect(fixtures.cacheClearer.isClearing).toBe(false);
|
|
196
|
-
|
|
197
|
+
|
|
197
198
|
// Reset
|
|
198
199
|
fixtures.cache.isSaving = false;
|
|
199
200
|
});
|
|
@@ -202,15 +203,15 @@ describe('CacheClearer', () => {
|
|
|
202
203
|
|
|
203
204
|
describe('Clear Cache Tool Handler', () => {
|
|
204
205
|
let fixtures;
|
|
205
|
-
|
|
206
|
+
|
|
206
207
|
beforeAll(async () => {
|
|
207
208
|
fixtures = await createTestFixtures({ workerThreads: 1 });
|
|
208
209
|
});
|
|
209
|
-
|
|
210
|
+
|
|
210
211
|
afterAll(async () => {
|
|
211
212
|
await cleanupFixtures(fixtures);
|
|
212
213
|
});
|
|
213
|
-
|
|
214
|
+
|
|
214
215
|
beforeEach(async () => {
|
|
215
216
|
fixtures.indexer.isIndexing = false;
|
|
216
217
|
fixtures.cache.isSaving = false;
|
|
@@ -220,7 +221,7 @@ describe('Clear Cache Tool Handler', () => {
|
|
|
220
221
|
describe('Tool Definition', () => {
|
|
221
222
|
it('should have correct tool definition', () => {
|
|
222
223
|
const toolDef = ClearCacheFeature.getToolDefinition();
|
|
223
|
-
|
|
224
|
+
|
|
224
225
|
expect(toolDef.name).toBe('c_clear_cache');
|
|
225
226
|
expect(toolDef.description).toContain('cache');
|
|
226
227
|
expect(toolDef.annotations.destructiveHint).toBe(true);
|
|
@@ -232,53 +233,53 @@ describe('Clear Cache Tool Handler', () => {
|
|
|
232
233
|
it('should return success message on cleared cache', async () => {
|
|
233
234
|
// Index first
|
|
234
235
|
await fixtures.indexer.indexAll(true);
|
|
235
|
-
|
|
236
|
+
|
|
236
237
|
const request = createMockRequest('c_clear_cache', {});
|
|
237
238
|
const result = await ClearCacheFeature.handleToolCall(request, fixtures.cacheClearer);
|
|
238
|
-
|
|
239
|
+
|
|
239
240
|
expect(result.content[0].text).toContain('Cache cleared successfully');
|
|
240
241
|
expect(result.content[0].text).toContain('Cache directory:');
|
|
241
242
|
});
|
|
242
|
-
|
|
243
|
+
|
|
243
244
|
it('should return error message when indexing is in progress', async () => {
|
|
244
245
|
// Simulate indexing
|
|
245
246
|
await clearTestCache(fixtures.config);
|
|
246
247
|
fixtures.cache.setVectorStore([]);
|
|
247
|
-
fixtures.cache.
|
|
248
|
-
|
|
248
|
+
fixtures.cache.clearFileHashes();
|
|
249
|
+
|
|
249
250
|
const indexPromise = fixtures.indexer.indexAll(true);
|
|
250
251
|
expect(fixtures.indexer.isIndexing).toBe(true);
|
|
251
|
-
|
|
252
|
+
|
|
252
253
|
const request = createMockRequest('c_clear_cache', {});
|
|
253
254
|
const result = await ClearCacheFeature.handleToolCall(request, fixtures.cacheClearer);
|
|
254
|
-
|
|
255
|
+
|
|
255
256
|
expect(result.content[0].text).toContain('Failed to clear cache');
|
|
256
257
|
expect(result.content[0].text).toContain('indexing is in progress');
|
|
257
|
-
|
|
258
|
+
|
|
258
259
|
await indexPromise;
|
|
259
260
|
});
|
|
260
|
-
|
|
261
|
+
|
|
261
262
|
it('should return error message when save is in progress', async () => {
|
|
262
263
|
fixtures.cache.isSaving = true;
|
|
263
|
-
|
|
264
|
+
|
|
264
265
|
const request = createMockRequest('c_clear_cache', {});
|
|
265
266
|
const result = await ClearCacheFeature.handleToolCall(request, fixtures.cacheClearer);
|
|
266
|
-
|
|
267
|
+
|
|
267
268
|
expect(result.content[0].text).toContain('Failed to clear cache');
|
|
268
269
|
expect(result.content[0].text).toContain('being saved');
|
|
269
|
-
|
|
270
|
+
|
|
270
271
|
fixtures.cache.isSaving = false;
|
|
271
272
|
});
|
|
272
|
-
|
|
273
|
+
|
|
273
274
|
it('should return error message when clear is already in progress', async () => {
|
|
274
275
|
fixtures.cacheClearer.isClearing = true;
|
|
275
|
-
|
|
276
|
+
|
|
276
277
|
const request = createMockRequest('c_clear_cache', {});
|
|
277
278
|
const result = await ClearCacheFeature.handleToolCall(request, fixtures.cacheClearer);
|
|
278
|
-
|
|
279
|
+
|
|
279
280
|
expect(result.content[0].text).toContain('Failed to clear cache');
|
|
280
281
|
expect(result.content[0].text).toContain('already in progress');
|
|
281
|
-
|
|
282
|
+
|
|
282
283
|
fixtures.cacheClearer.isClearing = false;
|
|
283
284
|
});
|
|
284
285
|
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure the code-review workflow includes the required production-ready guidance.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import fs from 'fs/promises';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const testDir = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const repoRoot = path.resolve(testDir, '..');
|
|
12
|
+
const workflowPath = path.join(repoRoot, '.agent', 'workflows', 'code-review.md');
|
|
13
|
+
|
|
14
|
+
async function readWorkflow() {
|
|
15
|
+
return fs.readFile(workflowPath, 'utf8');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('Code Review Workflow', () => {
|
|
19
|
+
it('includes scope control and line-by-line expectations', async () => {
|
|
20
|
+
const content = await readWorkflow();
|
|
21
|
+
|
|
22
|
+
expect(content).toContain('Review Scope & Context');
|
|
23
|
+
expect(content).toContain('For code <100 lines');
|
|
24
|
+
expect(content).toContain('For code >500 lines or critical systems');
|
|
25
|
+
expect(content).toContain('Review every function and critical code path');
|
|
26
|
+
expect(content).toContain('Group line-by-line findings by severity');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('includes fix plan effort scale and dependency tracking', async () => {
|
|
30
|
+
const content = await readWorkflow();
|
|
31
|
+
|
|
32
|
+
expect(content).toContain('Estimated effort per item');
|
|
33
|
+
expect(content).toContain('S (Small)');
|
|
34
|
+
expect(content).toContain('M (Medium)');
|
|
35
|
+
expect(content).toContain('L (Large)');
|
|
36
|
+
expect(content).toContain('Requires #N');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('includes patch, tests, length control, and follow-up guidance', async () => {
|
|
40
|
+
const content = await readWorkflow();
|
|
41
|
+
|
|
42
|
+
expect(content).toContain('unified diff format');
|
|
43
|
+
expect(content).toContain('ALL critical severity issues');
|
|
44
|
+
expect(content).toContain('unit tests');
|
|
45
|
+
expect(content).toContain('integration tests');
|
|
46
|
+
expect(content).toContain('Length Control');
|
|
47
|
+
expect(content).toContain('Follow-up Reviews');
|
|
48
|
+
expect(content).toContain('regression verification');
|
|
49
|
+
});
|
|
50
|
+
});
|