@softerist/heuristic-mcp 3.2.3 → 3.2.5

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 (46) hide show
  1. package/README.md +387 -376
  2. package/config.jsonc +800 -800
  3. package/features/ann-config.js +102 -110
  4. package/features/clear-cache.js +81 -84
  5. package/features/find-similar-code.js +265 -286
  6. package/features/hybrid-search.js +487 -536
  7. package/features/index-codebase.js +3146 -3271
  8. package/features/lifecycle.js +1011 -1063
  9. package/features/package-version.js +277 -291
  10. package/features/register.js +351 -370
  11. package/features/resources.js +115 -130
  12. package/features/set-workspace.js +214 -240
  13. package/index.js +788 -781
  14. package/lib/cache-ops.js +22 -22
  15. package/lib/cache-utils.js +465 -519
  16. package/lib/cache.js +1749 -1849
  17. package/lib/call-graph.js +396 -396
  18. package/lib/cli.js +232 -226
  19. package/lib/config.js +1483 -1495
  20. package/lib/constants.js +511 -493
  21. package/lib/embed-query-process.js +206 -212
  22. package/lib/embedding-process.js +434 -451
  23. package/lib/embedding-worker.js +862 -934
  24. package/lib/ignore-patterns.js +276 -316
  25. package/lib/json-worker.js +14 -14
  26. package/lib/json-writer.js +302 -310
  27. package/lib/logging.js +133 -127
  28. package/lib/memory-logger.js +13 -13
  29. package/lib/onnx-backend.js +188 -193
  30. package/lib/path-utils.js +18 -23
  31. package/lib/project-detector.js +82 -84
  32. package/lib/server-lifecycle.js +164 -147
  33. package/lib/settings-editor.js +738 -739
  34. package/lib/slice-normalize.js +25 -31
  35. package/lib/tokenizer.js +168 -203
  36. package/lib/utils.js +364 -409
  37. package/lib/vector-store-binary.js +973 -991
  38. package/lib/vector-store-sqlite.js +377 -414
  39. package/lib/workspace-env.js +32 -34
  40. package/mcp_config.json +9 -9
  41. package/package.json +86 -86
  42. package/scripts/clear-cache.js +20 -20
  43. package/scripts/download-model.js +43 -43
  44. package/scripts/mcp-launcher.js +49 -49
  45. package/scripts/postinstall.js +12 -12
  46. package/search-configs.js +36 -36
@@ -1,192 +1,170 @@
1
-
2
-
3
1
  import path from 'path';
4
2
  import fs from 'fs/promises';
5
3
  import { acquireWorkspaceLock, releaseWorkspaceLock } from '../lib/server-lifecycle.js';
6
4
  import { getWorkspaceCachePath } from '../lib/workspace-cache-key.js';
7
5
  import { cleanupStaleBinaryArtifacts } from '../lib/vector-store-binary.js';
8
-
9
-
6
+
10
7
  function getWorkspaceCacheDir(workspacePath, globalCacheDir) {
11
8
  return getWorkspaceCachePath(workspacePath, globalCacheDir);
12
9
  }
13
-
14
-
15
- export function getToolDefinition() {
16
- return {
17
- name: 'f_set_workspace',
18
- description:
19
- 'Changes the current workspace path at runtime. This updates the search directory and cache, and optionally triggers a full reindex. Useful for multi-project workflows.',
20
- inputSchema: {
21
- type: 'object',
22
- properties: {
23
- workspacePath: {
24
- type: 'string',
25
- description: 'Absolute path to the new workspace directory',
26
- },
27
- reindex: {
28
- type: 'boolean',
29
- description: 'Whether to trigger a full reindex after switching (default: true)',
30
- default: true,
31
- },
32
- },
33
- required: ['workspacePath'],
34
- },
35
- annotations: {
36
- title: 'Set Workspace',
37
- readOnlyHint: false,
38
- destructiveHint: false,
39
- idempotentHint: true,
40
- openWorldHint: false,
41
- },
42
- };
43
- }
44
-
45
-
46
- export class SetWorkspaceFeature {
47
- constructor(config, cache, indexer, getGlobalCacheDir) {
48
- this.config = config;
49
- this.cache = cache;
50
- this.indexer = indexer;
51
- this.getGlobalCacheDir = getGlobalCacheDir;
52
- }
53
-
54
- async execute({ workspacePath, reindex = true }) {
55
-
56
- if (!workspacePath || typeof workspacePath !== 'string') {
57
- return {
58
- success: false,
59
- error: 'workspacePath is required and must be a string',
60
- };
61
- }
62
-
63
- const normalizedPath = path.resolve(workspacePath);
64
-
65
-
66
- try {
67
- const stat = await fs.stat(normalizedPath);
68
- if (!stat.isDirectory()) {
69
- return {
70
- success: false,
71
- error: `Path is not a directory: ${normalizedPath}`,
72
- };
73
- }
74
- } catch (err) {
75
- return {
76
- success: false,
77
- error: `Cannot access directory: ${normalizedPath} (${err.message})`,
78
- };
79
- }
80
-
81
- const previousWorkspace = this.config.searchDirectory;
82
- const previousCache = this.config.cacheDirectory;
83
-
84
-
85
- this.config.searchDirectory = normalizedPath;
86
-
87
-
88
- const globalCacheDir = this.getGlobalCacheDir();
89
- let newCacheDir = getWorkspaceCacheDir(normalizedPath, globalCacheDir);
90
-
91
-
92
- const legacyPath = path.join(normalizedPath, '.smart-coding-cache');
93
- try {
94
- const legacyStats = await fs.stat(legacyPath);
95
- if (legacyStats.isDirectory()) {
96
- newCacheDir = legacyPath;
97
- }
98
- } catch {
99
-
100
- }
101
- this.config.cacheDirectory = newCacheDir;
102
-
103
-
104
- try {
105
- await fs.mkdir(newCacheDir, { recursive: true });
106
- } catch (err) {
107
-
108
- this.config.searchDirectory = previousWorkspace;
109
- this.config.cacheDirectory = previousCache;
110
- return {
111
- success: false,
112
- error: `Failed to create cache directory: ${err.message}`,
113
- };
114
- }
115
-
116
-
117
- const lock = await acquireWorkspaceLock({
118
- cacheDirectory: newCacheDir,
119
- workspaceDir: normalizedPath,
120
- });
121
- if (!lock.acquired) {
122
-
123
- this.config.searchDirectory = previousWorkspace;
124
- this.config.cacheDirectory = previousCache;
125
- return {
126
- success: false,
127
- error: `Workspace is already locked by another server (pid ${lock.ownerPid ?? 'unknown'})`,
128
- };
129
- }
130
- let indexerUpdateError = null;
131
-
132
-
133
- if (this.indexer) {
134
- if (typeof this.indexer.terminateWorkers === 'function') {
135
- try {
136
- await this.indexer.terminateWorkers();
137
- } catch (err) {
138
- console.warn(`[SetWorkspace] Failed to terminate workers: ${err.message}`);
139
- }
140
- }
141
- try {
142
- if (typeof this.indexer.updateWorkspaceState === 'function') {
143
- await this.indexer.updateWorkspaceState({ restartWatcher: true });
144
- } else {
145
- this.indexer.workspaceRoot = normalizedPath;
146
- this.indexer.workspaceRootReal = null;
147
- if (this.config.watchFiles && typeof this.indexer.setupFileWatcher === 'function') {
148
- await this.indexer.setupFileWatcher();
149
- }
150
- }
151
- } catch (err) {
152
- indexerUpdateError = err;
153
- }
154
- }
155
-
156
- if (indexerUpdateError) {
157
-
158
- this.config.searchDirectory = previousWorkspace;
159
- this.config.cacheDirectory = previousCache;
160
- await releaseWorkspaceLock({ cacheDirectory: newCacheDir });
161
- if (this.indexer) {
162
- try {
163
- if (typeof this.indexer.updateWorkspaceState === 'function') {
164
- await this.indexer.updateWorkspaceState({ restartWatcher: true });
165
- } else {
166
- this.indexer.workspaceRoot = previousWorkspace;
167
- this.indexer.workspaceRootReal = null;
168
- if (this.config.watchFiles && typeof this.indexer.setupFileWatcher === 'function') {
169
- await this.indexer.setupFileWatcher();
170
- }
171
- }
172
- } catch (rollbackErr) {
173
- console.warn(
174
- `[SetWorkspace] Failed to rollback indexer state: ${rollbackErr.message}`
175
- );
176
- }
177
- }
178
- return {
179
- success: false,
180
- error: `Failed to update workspace state: ${indexerUpdateError.message}`,
181
- };
182
- }
183
-
184
-
185
- if (previousCache) {
186
- await releaseWorkspaceLock({ cacheDirectory: previousCache });
187
- }
188
-
189
-
10
+
11
+ export function getToolDefinition() {
12
+ return {
13
+ name: 'f_set_workspace',
14
+ description:
15
+ 'Changes the current workspace path at runtime. This updates the search directory and cache, and optionally triggers a full reindex. Useful for multi-project workflows.',
16
+ inputSchema: {
17
+ type: 'object',
18
+ properties: {
19
+ workspacePath: {
20
+ type: 'string',
21
+ description: 'Absolute path to the new workspace directory',
22
+ },
23
+ reindex: {
24
+ type: 'boolean',
25
+ description: 'Whether to trigger a full reindex after switching (default: true)',
26
+ default: true,
27
+ },
28
+ },
29
+ required: ['workspacePath'],
30
+ },
31
+ annotations: {
32
+ title: 'Set Workspace',
33
+ readOnlyHint: false,
34
+ destructiveHint: false,
35
+ idempotentHint: true,
36
+ openWorldHint: false,
37
+ },
38
+ };
39
+ }
40
+
41
+ export class SetWorkspaceFeature {
42
+ constructor(config, cache, indexer, getGlobalCacheDir) {
43
+ this.config = config;
44
+ this.cache = cache;
45
+ this.indexer = indexer;
46
+ this.getGlobalCacheDir = getGlobalCacheDir;
47
+ }
48
+
49
+ async execute({ workspacePath, reindex = true }) {
50
+ if (!workspacePath || typeof workspacePath !== 'string') {
51
+ return {
52
+ success: false,
53
+ error: 'workspacePath is required and must be a string',
54
+ };
55
+ }
56
+
57
+ const normalizedPath = path.resolve(workspacePath);
58
+
59
+ try {
60
+ const stat = await fs.stat(normalizedPath);
61
+ if (!stat.isDirectory()) {
62
+ return {
63
+ success: false,
64
+ error: `Path is not a directory: ${normalizedPath}`,
65
+ };
66
+ }
67
+ } catch (err) {
68
+ return {
69
+ success: false,
70
+ error: `Cannot access directory: ${normalizedPath} (${err.message})`,
71
+ };
72
+ }
73
+
74
+ const previousWorkspace = this.config.searchDirectory;
75
+ const previousCache = this.config.cacheDirectory;
76
+
77
+ this.config.searchDirectory = normalizedPath;
78
+
79
+ const globalCacheDir = this.getGlobalCacheDir();
80
+ let newCacheDir = getWorkspaceCacheDir(normalizedPath, globalCacheDir);
81
+
82
+ const legacyPath = path.join(normalizedPath, '.smart-coding-cache');
83
+ try {
84
+ const legacyStats = await fs.stat(legacyPath);
85
+ if (legacyStats.isDirectory()) {
86
+ newCacheDir = legacyPath;
87
+ }
88
+ } catch {}
89
+ this.config.cacheDirectory = newCacheDir;
90
+
91
+ try {
92
+ await fs.mkdir(newCacheDir, { recursive: true });
93
+ } catch (err) {
94
+ this.config.searchDirectory = previousWorkspace;
95
+ this.config.cacheDirectory = previousCache;
96
+ return {
97
+ success: false,
98
+ error: `Failed to create cache directory: ${err.message}`,
99
+ };
100
+ }
101
+
102
+ const lock = await acquireWorkspaceLock({
103
+ cacheDirectory: newCacheDir,
104
+ workspaceDir: normalizedPath,
105
+ });
106
+ if (!lock.acquired) {
107
+ this.config.searchDirectory = previousWorkspace;
108
+ this.config.cacheDirectory = previousCache;
109
+ return {
110
+ success: false,
111
+ error: `Workspace is already locked by another server (pid ${lock.ownerPid ?? 'unknown'})`,
112
+ };
113
+ }
114
+ let indexerUpdateError = null;
115
+
116
+ if (this.indexer) {
117
+ if (typeof this.indexer.terminateWorkers === 'function') {
118
+ try {
119
+ await this.indexer.terminateWorkers();
120
+ } catch (err) {
121
+ console.warn(`[SetWorkspace] Failed to terminate workers: ${err.message}`);
122
+ }
123
+ }
124
+ try {
125
+ if (typeof this.indexer.updateWorkspaceState === 'function') {
126
+ await this.indexer.updateWorkspaceState({ restartWatcher: true });
127
+ } else {
128
+ this.indexer.workspaceRoot = normalizedPath;
129
+ this.indexer.workspaceRootReal = null;
130
+ if (this.config.watchFiles && typeof this.indexer.setupFileWatcher === 'function') {
131
+ await this.indexer.setupFileWatcher();
132
+ }
133
+ }
134
+ } catch (err) {
135
+ indexerUpdateError = err;
136
+ }
137
+ }
138
+
139
+ if (indexerUpdateError) {
140
+ this.config.searchDirectory = previousWorkspace;
141
+ this.config.cacheDirectory = previousCache;
142
+ await releaseWorkspaceLock({ cacheDirectory: newCacheDir });
143
+ if (this.indexer) {
144
+ try {
145
+ if (typeof this.indexer.updateWorkspaceState === 'function') {
146
+ await this.indexer.updateWorkspaceState({ restartWatcher: true });
147
+ } else {
148
+ this.indexer.workspaceRoot = previousWorkspace;
149
+ this.indexer.workspaceRootReal = null;
150
+ if (this.config.watchFiles && typeof this.indexer.setupFileWatcher === 'function') {
151
+ await this.indexer.setupFileWatcher();
152
+ }
153
+ }
154
+ } catch (rollbackErr) {
155
+ console.warn(`[SetWorkspace] Failed to rollback indexer state: ${rollbackErr.message}`);
156
+ }
157
+ }
158
+ return {
159
+ success: false,
160
+ error: `Failed to update workspace state: ${indexerUpdateError.message}`,
161
+ };
162
+ }
163
+
164
+ if (previousCache) {
165
+ await releaseWorkspaceLock({ cacheDirectory: previousCache });
166
+ }
167
+
190
168
  if (this.cache && typeof this.cache.load === 'function') {
191
169
  try {
192
170
  if (this.config.vectorStoreFormat === 'binary') {
@@ -196,62 +174,58 @@ export class SetWorkspaceFeature {
196
174
  } catch (err) {
197
175
  console.warn(`[SetWorkspace] Failed to load cache: ${err.message}`);
198
176
  }
199
- }
200
-
201
-
202
- let reindexStatus = null;
203
- if (reindex && this.indexer && typeof this.indexer.indexAll === 'function') {
204
- try {
205
-
206
- this.indexer.indexAll().catch((err) => {
207
- console.warn(`[SetWorkspace] Reindex failed: ${err.message}`);
208
- });
209
- reindexStatus = 'started';
210
- } catch (err) {
211
- reindexStatus = `failed: ${err.message}`;
212
- }
213
- } else if (!reindex) {
214
- reindexStatus = 'skipped';
215
- }
216
-
217
- return {
218
- success: true,
219
- previousWorkspace,
220
- newWorkspace: normalizedPath,
221
- cacheDirectory: newCacheDir,
222
- reindexStatus,
223
- };
224
- }
225
- }
226
-
227
-
228
- export function createHandleToolCall(featureInstance) {
229
- return async (request) => {
230
- const args = request.params?.arguments || {};
231
- const { workspacePath, reindex } = args;
232
-
233
- const result = await featureInstance.execute({
234
- workspacePath,
235
- reindex: reindex !== false,
236
- });
237
-
238
- if (result.success) {
239
- let message = `✓ Workspace switched to: **${result.newWorkspace}**\n`;
240
- message += `\n- Previous: \`${result.previousWorkspace || '(none)'}\``;
241
- message += `\n- Cache: \`${result.cacheDirectory}\``;
242
- if (result.reindexStatus) {
243
- message += `\n- Reindex: ${result.reindexStatus}`;
244
- }
245
- return {
246
- content: [{ type: 'text', text: message }],
247
- };
248
- } else {
249
- return {
250
- content: [{ type: 'text', text: `Error: ${result.error}` }],
251
- };
252
- }
253
- };
254
- }
255
-
256
-
257
- export { getWorkspaceCacheDir };
177
+ }
178
+
179
+ let reindexStatus = null;
180
+ if (reindex && this.indexer && typeof this.indexer.indexAll === 'function') {
181
+ try {
182
+ this.indexer.indexAll().catch((err) => {
183
+ console.warn(`[SetWorkspace] Reindex failed: ${err.message}`);
184
+ });
185
+ reindexStatus = 'started';
186
+ } catch (err) {
187
+ reindexStatus = `failed: ${err.message}`;
188
+ }
189
+ } else if (!reindex) {
190
+ reindexStatus = 'skipped';
191
+ }
192
+
193
+ return {
194
+ success: true,
195
+ previousWorkspace,
196
+ newWorkspace: normalizedPath,
197
+ cacheDirectory: newCacheDir,
198
+ reindexStatus,
199
+ };
200
+ }
201
+ }
202
+
203
+ export function createHandleToolCall(featureInstance) {
204
+ return async (request) => {
205
+ const args = request.params?.arguments || {};
206
+ const { workspacePath, reindex } = args;
207
+
208
+ const result = await featureInstance.execute({
209
+ workspacePath,
210
+ reindex: reindex !== false,
211
+ });
212
+
213
+ if (result.success) {
214
+ let message = `✓ Workspace switched to: **${result.newWorkspace}**\n`;
215
+ message += `\n- Previous: \`${result.previousWorkspace || '(none)'}\``;
216
+ message += `\n- Cache: \`${result.cacheDirectory}\``;
217
+ if (result.reindexStatus) {
218
+ message += `\n- Reindex: ${result.reindexStatus}`;
219
+ }
220
+ return {
221
+ content: [{ type: 'text', text: message }],
222
+ };
223
+ } else {
224
+ return {
225
+ content: [{ type: 'text', text: `Error: ${result.error}` }],
226
+ };
227
+ }
228
+ };
229
+ }
230
+
231
+ export { getWorkspaceCacheDir };