@softerist/heuristic-mcp 2.1.47 → 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.
Files changed (109) hide show
  1. package/.agent/workflows/code-review.md +60 -0
  2. package/.prettierrc +7 -0
  3. package/ARCHITECTURE.md +105 -170
  4. package/CONTRIBUTING.md +32 -113
  5. package/GEMINI.md +73 -0
  6. package/LICENSE +21 -21
  7. package/README.md +161 -54
  8. package/config.json +876 -75
  9. package/debug-pids.js +27 -0
  10. package/eslint.config.js +36 -0
  11. package/features/ann-config.js +37 -26
  12. package/features/clear-cache.js +28 -19
  13. package/features/find-similar-code.js +142 -66
  14. package/features/hybrid-search.js +253 -93
  15. package/features/index-codebase.js +1455 -394
  16. package/features/lifecycle.js +813 -180
  17. package/features/register.js +58 -52
  18. package/index.js +450 -306
  19. package/lib/cache-ops.js +22 -0
  20. package/lib/cache-utils.js +68 -0
  21. package/lib/cache.js +1392 -587
  22. package/lib/call-graph.js +165 -50
  23. package/lib/cli.js +154 -0
  24. package/lib/config.js +462 -121
  25. package/lib/embedding-process.js +77 -0
  26. package/lib/embedding-worker.js +545 -30
  27. package/lib/ignore-patterns.js +61 -59
  28. package/lib/json-worker.js +14 -0
  29. package/lib/json-writer.js +344 -0
  30. package/lib/logging.js +88 -0
  31. package/lib/memory-logger.js +13 -0
  32. package/lib/project-detector.js +13 -17
  33. package/lib/server-lifecycle.js +38 -0
  34. package/lib/settings-editor.js +645 -0
  35. package/lib/tokenizer.js +207 -104
  36. package/lib/utils.js +273 -198
  37. package/lib/vector-store-binary.js +592 -0
  38. package/mcp_config.example.json +13 -0
  39. package/package.json +13 -2
  40. package/scripts/clear-cache.js +6 -17
  41. package/scripts/download-model.js +14 -9
  42. package/scripts/postinstall.js +5 -5
  43. package/search-configs.js +36 -0
  44. package/test/ann-config.test.js +179 -0
  45. package/test/ann-fallback.test.js +6 -6
  46. package/test/binary-store.test.js +69 -0
  47. package/test/cache-branches.test.js +120 -0
  48. package/test/cache-errors.test.js +264 -0
  49. package/test/cache-extra.test.js +300 -0
  50. package/test/cache-helpers.test.js +205 -0
  51. package/test/cache-hnsw-failure.test.js +40 -0
  52. package/test/cache-json-worker.test.js +190 -0
  53. package/test/cache-worker.test.js +102 -0
  54. package/test/cache.test.js +443 -0
  55. package/test/call-graph.test.js +103 -4
  56. package/test/clear-cache.test.js +69 -68
  57. package/test/code-review-workflow.test.js +50 -0
  58. package/test/config.test.js +418 -0
  59. package/test/coverage-gap.test.js +497 -0
  60. package/test/coverage-maximizer.test.js +236 -0
  61. package/test/debug-analysis.js +107 -0
  62. package/test/embedding-model.test.js +173 -103
  63. package/test/embedding-worker-extra.test.js +272 -0
  64. package/test/embedding-worker.test.js +158 -0
  65. package/test/features.test.js +139 -0
  66. package/test/final-boost.test.js +271 -0
  67. package/test/final-polish.test.js +183 -0
  68. package/test/final.test.js +95 -0
  69. package/test/find-similar-code.test.js +191 -0
  70. package/test/helpers.js +92 -11
  71. package/test/helpers.test.js +46 -0
  72. package/test/hybrid-search-basic.test.js +62 -0
  73. package/test/hybrid-search-branch.test.js +202 -0
  74. package/test/hybrid-search-callgraph.test.js +229 -0
  75. package/test/hybrid-search-extra.test.js +81 -0
  76. package/test/hybrid-search.test.js +484 -71
  77. package/test/index-cli.test.js +520 -0
  78. package/test/index-codebase-batch.test.js +119 -0
  79. package/test/index-codebase-branches.test.js +585 -0
  80. package/test/index-codebase-core.test.js +1032 -0
  81. package/test/index-codebase-edge-cases.test.js +254 -0
  82. package/test/index-codebase-errors.test.js +132 -0
  83. package/test/index-codebase-gap.test.js +239 -0
  84. package/test/index-codebase-lines.test.js +151 -0
  85. package/test/index-codebase-watcher.test.js +259 -0
  86. package/test/index-codebase-zone.test.js +259 -0
  87. package/test/index-codebase.test.js +371 -69
  88. package/test/index-memory.test.js +220 -0
  89. package/test/indexer-detailed.test.js +176 -0
  90. package/test/integration.test.js +148 -92
  91. package/test/json-worker.test.js +50 -0
  92. package/test/lifecycle.test.js +541 -0
  93. package/test/master.test.js +198 -0
  94. package/test/perfection.test.js +349 -0
  95. package/test/project-detector.test.js +65 -0
  96. package/test/register.test.js +262 -0
  97. package/test/tokenizer.test.js +55 -93
  98. package/test/ultra-maximizer.test.js +116 -0
  99. package/test/utils-branches.test.js +161 -0
  100. package/test/utils-extra.test.js +116 -0
  101. package/test/utils.test.js +131 -0
  102. package/test/verify_fixes.js +76 -0
  103. package/test/worker-errors.test.js +96 -0
  104. package/test/worker-init.test.js +102 -0
  105. package/test/worker_throttling.test.js +93 -0
  106. package/tools/scripts/benchmark-search.js +95 -0
  107. package/tools/scripts/cache-stats.js +71 -0
  108. package/tools/scripts/manual-search.js +34 -0
  109. package/vitest.config.js +19 -9
@@ -4,7 +4,7 @@ import { writeFileSync, existsSync, statSync } from 'fs';
4
4
  import path from 'path';
5
5
  import os from 'os';
6
6
  import { fileURLToPath } from 'url';
7
-
7
+ import { parseJsonc, upsertMcpServerEntryInText } from '../lib/settings-editor.js';
8
8
 
9
9
  // Detect which IDE is running the install
10
10
  function detectCurrentIDE() {
@@ -18,17 +18,16 @@ function detectCurrentIDE() {
18
18
 
19
19
  // Fallback: Check for Antigravity directory presence
20
20
  try {
21
- const agPath = path.join(os.homedir(), '.gemini', 'antigravity');
22
- if (existsSync(agPath) || (statSync && statSync(agPath).isDirectory())) {
23
- return 'Antigravity';
24
- }
25
- } catch (e) {}
21
+ const agPath = path.join(os.homedir(), '.gemini', 'antigravity');
22
+ if (existsSync(agPath) || (statSync && statSync(agPath).isDirectory())) {
23
+ return 'Antigravity';
24
+ }
25
+ } catch (_e) { /* ignore */ }
26
26
 
27
27
  // Claude Desktop doesn't have a known env var, so we rely on existing config detection
28
28
  return null;
29
29
  }
30
30
 
31
-
32
31
  // Known config paths for different IDEs
33
32
  function getConfigPaths() {
34
33
  const platform = process.platform;
@@ -39,19 +38,25 @@ function getConfigPaths() {
39
38
  // Antigravity - dedicated mcp_config.json
40
39
  allPaths.push({
41
40
  name: 'Antigravity',
42
- path: path.join(home, '.gemini', 'antigravity', 'mcp_config.json')
41
+ path: path.join(home, '.gemini', 'antigravity', 'mcp_config.json'),
43
42
  });
44
43
 
45
44
  // Claude Desktop - dedicated config file
46
45
  if (platform === 'darwin') {
47
46
  allPaths.push({
48
47
  name: 'Claude Desktop',
49
- path: path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')
48
+ path: path.join(
49
+ home,
50
+ 'Library',
51
+ 'Application Support',
52
+ 'Claude',
53
+ 'claude_desktop_config.json'
54
+ ),
50
55
  });
51
56
  } else if (platform === 'win32') {
52
57
  allPaths.push({
53
58
  name: 'Claude Desktop',
54
- path: path.join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json')
59
+ path: path.join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json'),
55
60
  });
56
61
  }
57
62
 
@@ -59,17 +64,17 @@ function getConfigPaths() {
59
64
  if (platform === 'darwin') {
60
65
  allPaths.push({
61
66
  name: 'Cursor',
62
- path: path.join(home, 'Library', 'Application Support', 'Cursor', 'User', 'settings.json')
67
+ path: path.join(home, 'Library', 'Application Support', 'Cursor', 'User', 'settings.json'),
63
68
  });
64
69
  } else if (platform === 'win32') {
65
70
  allPaths.push({
66
71
  name: 'Cursor',
67
- path: path.join(process.env.APPDATA || '', 'Cursor', 'User', 'settings.json')
72
+ path: path.join(process.env.APPDATA || '', 'Cursor', 'User', 'settings.json'),
68
73
  });
69
74
  } else {
70
75
  allPaths.push({
71
76
  name: 'Cursor',
72
- path: path.join(home, '.config', 'Cursor', 'User', 'settings.json')
77
+ path: path.join(home, '.config', 'Cursor', 'User', 'settings.json'),
73
78
  });
74
79
  }
75
80
 
@@ -79,12 +84,10 @@ function getConfigPaths() {
79
84
 
80
85
  if (currentIDE) {
81
86
  // IDE detected - return only that IDE with permission to create
82
- return allPaths
83
- .filter(p => p.name === currentIDE)
84
- .map(p => ({ ...p, canCreate: true }));
87
+ return allPaths.filter((p) => p.name === currentIDE).map((p) => ({ ...p, canCreate: true }));
85
88
  } else {
86
89
  // No IDE detected - return all but don't create new configs
87
- return allPaths.map(p => ({ ...p, canCreate: false }));
90
+ return allPaths.map((p) => ({ ...p, canCreate: false }));
88
91
  }
89
92
  }
90
93
 
@@ -92,11 +95,11 @@ function getConfigPaths() {
92
95
  function forceLog(message) {
93
96
  try {
94
97
  if (process.platform !== 'win32') {
95
- fs.writeFileSync('/dev/tty', message + '\n');
98
+ writeFileSync('/dev/tty', message + '\n');
96
99
  } else {
97
100
  console.error(message);
98
101
  }
99
- } catch (e) {
102
+ } catch (_e) {
100
103
  console.error(message);
101
104
  }
102
105
  }
@@ -109,16 +112,15 @@ export async function register(filter = null) {
109
112
  // For Antigravity, we MUST use absolute path because ${workspaceFolder} variable expansion
110
113
  // is not supported in the current version, and '.' uses the wrong CWD.
111
114
  // Use INIT_CWD (where npm install was run) if available, otherwise cwd.
112
- const workspacePath = currentIDE === 'Antigravity' ? (process.env.INIT_CWD || process.cwd()) : '.';
115
+ const workspacePath =
116
+ currentIDE === 'Antigravity' ? process.env.INIT_CWD || process.cwd() : '${workspaceFolder}';
113
117
 
114
118
  const serverConfig = {
115
119
  command: binaryPath,
116
- args: [scriptPath, "--workspace", workspacePath],
120
+ args: [scriptPath, '--workspace', workspacePath],
117
121
  disabled: false,
118
- autoRegistered: true // Marker to know we did this
119
122
  };
120
123
 
121
-
122
124
  const configPaths = getConfigPaths();
123
125
  let registeredCount = 0;
124
126
 
@@ -131,7 +133,6 @@ export async function register(filter = null) {
131
133
 
132
134
  try {
133
135
  // Check if file exists - create if canCreate is true for this IDE
134
- let config = {};
135
136
  let fileExists = true;
136
137
 
137
138
  try {
@@ -145,7 +146,9 @@ export async function register(filter = null) {
145
146
  await fs.mkdir(path.dirname(configPath), { recursive: true });
146
147
  forceLog(`[Auto-Register] Creating ${name} config at ${configPath}`);
147
148
  } catch (mkdirErr) {
148
- forceLog(`[Auto-Register] Skipped ${name}: Cannot create config directory: ${mkdirErr.message}`);
149
+ forceLog(
150
+ `[Auto-Register] Skipped ${name}: Cannot create config directory: ${mkdirErr.message}`
151
+ );
149
152
  continue;
150
153
  }
151
154
  } else {
@@ -154,37 +157,33 @@ export async function register(filter = null) {
154
157
  }
155
158
  }
156
159
 
157
- // Read existing config if file exists
160
+ let content = '';
158
161
  if (fileExists) {
159
- const content = await fs.readFile(configPath, 'utf-8');
160
- try {
161
- if (!content || content.trim() === '') {
162
- // Empty file - treat as new
163
- config = {};
164
- } else {
165
- config = JSON.parse(content);
162
+ content = await fs.readFile(configPath, 'utf-8');
163
+ if (content.trim()) {
164
+ const parsed = parseJsonc(content);
165
+ if (!parsed) {
166
+ forceLog(
167
+ `[Auto-Register] Warning: ${name} config is not valid JSON/JSONC; skipping to avoid data loss.`
168
+ );
169
+ continue;
166
170
  }
167
- } catch (e) {
168
- forceLog(`[Auto-Register] Warning: Corrupt/empty ${name} config, resetting to default. Error: ${e.message}`);
169
- config = {};
170
171
  }
171
-
172
172
  }
173
173
 
174
- // Init mcpServers if missing
175
- if (!config.mcpServers) {
176
- config.mcpServers = {};
174
+ const updated = upsertMcpServerEntryInText(content, 'heuristic-mcp', serverConfig);
175
+ if (!updated) {
176
+ forceLog(
177
+ `[Auto-Register] Warning: Failed to update ${name} config (could not locate root object).`
178
+ );
179
+ continue;
177
180
  }
178
181
 
179
- // Inject configuration
180
- config.mcpServers['heuristic-mcp'] = serverConfig;
181
-
182
182
  // Write back synchronously to avoid race conditions
183
- writeFileSync(configPath, JSON.stringify(config, null, 2));
183
+ writeFileSync(configPath, updated);
184
184
 
185
185
  forceLog(`\x1b[32m[Auto-Register] ✅ Successfully registered with ${name}\x1b[0m`);
186
186
  registeredCount++;
187
-
188
187
  } catch (err) {
189
188
  forceLog(`[Auto-Register] Failed to register with ${name}: ${err.message}`);
190
189
  }
@@ -192,7 +191,9 @@ export async function register(filter = null) {
192
191
 
193
192
  if (registeredCount === 0) {
194
193
  forceLog(`[Auto-Register] No compatible IDE configurations found to update.`);
195
- forceLog(`[Auto-Register] Manual Config:\n${JSON.stringify({ mcpServers: { "heuristic-mcp": serverConfig } }, null, 2)}`);
194
+ forceLog(
195
+ `[Auto-Register] Manual Config:\n${JSON.stringify({ mcpServers: { 'heuristic-mcp': serverConfig } }, null, 2)}`
196
+ );
196
197
  } else {
197
198
  // Friendly Banner (Using forceLog to bypass npm stdout suppression)
198
199
  forceLog('\n\x1b[36m' + '='.repeat(60));
@@ -201,11 +202,15 @@ export async function register(filter = null) {
201
202
 
202
203
  // Show important paths
203
204
  const home = os.homedir();
204
- const cacheRoot = process.platform === 'win32'
205
- ? path.join(process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local'), 'heuristic-mcp')
205
+ const cacheRoot =
206
+ process.platform === 'win32'
207
+ ? path.join(
208
+ process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local'),
209
+ 'heuristic-mcp'
210
+ )
206
211
  : process.platform === 'darwin'
207
- ? path.join(home, 'Library', 'Caches', 'heuristic-mcp')
208
- : path.join(process.env.XDG_CACHE_HOME || path.join(home, '.cache'), 'heuristic-mcp');
212
+ ? path.join(home, 'Library', 'Caches', 'heuristic-mcp')
213
+ : path.join(process.env.XDG_CACHE_HOME || path.join(home, '.cache'), 'heuristic-mcp');
209
214
 
210
215
  forceLog(`
211
216
  \x1b[33mACTION REQUIRED:\x1b[0m
@@ -218,9 +223,10 @@ export async function register(filter = null) {
218
223
  - \x1b[1mUsage:\x1b[0m You can work while it indexes (it catches up!).
219
224
 
220
225
  \x1b[90mPATHS:\x1b[0m
221
- - \x1b[1mMCP Config:\x1b[0m ${configPaths.map(p => p.path).join(', ')}
226
+ - \x1b[1mMCP Config:\x1b[0m ${configPaths.map((p) => p.path).join(', ')}
222
227
  - \x1b[1mCache:\x1b[0m ${cacheRoot}
223
- - \x1b[1mCheck status:\x1b[0m heuristic-mcp --logs
228
+ - \x1b[1mCheck status:\x1b[0m heuristic-mcp --status
229
+ - \x1b[1mView logs:\x1b[0m heuristic-mcp --logs
224
230
 
225
231
  \x1b[36mHappy Coding! 🤖\x1b[0m
226
232
  `);