@softerist/heuristic-mcp 3.0.15 → 3.0.16

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 (49) hide show
  1. package/README.md +104 -104
  2. package/config.jsonc +173 -173
  3. package/features/ann-config.js +131 -0
  4. package/features/clear-cache.js +84 -0
  5. package/features/find-similar-code.js +291 -0
  6. package/features/hybrid-search.js +544 -0
  7. package/features/index-codebase.js +3268 -0
  8. package/features/lifecycle.js +1189 -0
  9. package/features/package-version.js +302 -0
  10. package/features/register.js +408 -0
  11. package/features/resources.js +156 -0
  12. package/features/set-workspace.js +265 -0
  13. package/index.js +96 -96
  14. package/lib/cache-ops.js +22 -22
  15. package/lib/cache-utils.js +565 -565
  16. package/lib/cache.js +1870 -1870
  17. package/lib/call-graph.js +396 -396
  18. package/lib/cli.js +1 -1
  19. package/lib/config.js +517 -517
  20. package/lib/constants.js +39 -39
  21. package/lib/embed-query-process.js +7 -7
  22. package/lib/embedding-process.js +7 -7
  23. package/lib/embedding-worker.js +299 -299
  24. package/lib/ignore-patterns.js +316 -316
  25. package/lib/json-worker.js +14 -14
  26. package/lib/json-writer.js +337 -337
  27. package/lib/logging.js +164 -164
  28. package/lib/memory-logger.js +13 -13
  29. package/lib/onnx-backend.js +193 -193
  30. package/lib/project-detector.js +84 -84
  31. package/lib/server-lifecycle.js +165 -165
  32. package/lib/settings-editor.js +754 -754
  33. package/lib/tokenizer.js +256 -256
  34. package/lib/utils.js +428 -428
  35. package/lib/vector-store-binary.js +627 -627
  36. package/lib/vector-store-sqlite.js +95 -95
  37. package/lib/workspace-env.js +28 -28
  38. package/mcp_config.json +9 -9
  39. package/package.json +86 -75
  40. package/scripts/clear-cache.js +20 -0
  41. package/scripts/download-model.js +43 -0
  42. package/scripts/mcp-launcher.js +49 -0
  43. package/scripts/postinstall.js +12 -0
  44. package/search-configs.js +36 -36
  45. package/.prettierrc +0 -7
  46. package/debug-pids.js +0 -30
  47. package/eslint.config.js +0 -36
  48. package/specs/plan.md +0 -23
  49. package/vitest.config.js +0 -39
@@ -1,165 +1,165 @@
1
- import fs from 'fs/promises';
2
- import fsSync from 'fs';
3
- import path from 'path';
4
- import os from 'os';
5
-
6
- function isTestEnv() {
7
- return process.env.VITEST === 'true' || process.env.NODE_ENV === 'test';
8
- }
9
-
10
- function getPidFilePath({ pidFileName, cacheDirectory }) {
11
- if (cacheDirectory) {
12
- return path.join(cacheDirectory, pidFileName);
13
- }
14
- return path.join(os.homedir(), pidFileName);
15
- }
16
-
17
- export async function setupPidFile({
18
- pidFileName = '.heuristic-mcp.pid',
19
- cacheDirectory = null,
20
- } = {}) {
21
- if (isTestEnv()) {
22
- return null;
23
- }
24
-
25
- const pidPath = getPidFilePath({ pidFileName, cacheDirectory });
26
- if (cacheDirectory) {
27
- try {
28
- await fs.mkdir(cacheDirectory, { recursive: true });
29
- } catch {
30
- // ignore mkdir errors and attempt to write anyway
31
- }
32
- }
33
- // Clean up stale PID file if present
34
- try {
35
- const raw = await fs.readFile(pidPath, 'utf-8');
36
- const existingPid = parseInt(String(raw).trim(), 10);
37
- if (Number.isInteger(existingPid) && !isProcessRunning(existingPid)) {
38
- await fs.unlink(pidPath).catch(() => {});
39
- }
40
- } catch {
41
- // ignore missing/invalid pid file
42
- }
43
- try {
44
- await fs.writeFile(pidPath, `${process.pid}`, 'utf-8');
45
- } catch (err) {
46
- console.error(`[Server] Warning: Failed to write PID file: ${err.message}`);
47
- return null;
48
- }
49
-
50
- const cleanup = () => {
51
- try {
52
- fsSync.unlinkSync(pidPath);
53
- } catch {
54
- // ignore
55
- }
56
- };
57
-
58
- process.on('exit', cleanup);
59
- return pidPath;
60
- }
61
-
62
- export function registerSignalHandlers(handler) {
63
- process.on('SIGINT', () => handler('SIGINT'));
64
- process.on('SIGTERM', () => handler('SIGTERM'));
65
- }
66
-
67
- function isProcessRunning(pid) {
68
- try {
69
- process.kill(pid, 0);
70
- return true;
71
- } catch (err) {
72
- // On Windows, EPERM can happen even if process exists.
73
- if (err && err.code === 'EPERM') {
74
- return true;
75
- }
76
- return false;
77
- }
78
- }
79
-
80
- export async function acquireWorkspaceLock({ cacheDirectory, workspaceDir = null } = {}) {
81
- if (!cacheDirectory || isTestEnv()) {
82
- return { acquired: true, lockPath: null };
83
- }
84
-
85
- await fs.mkdir(cacheDirectory, { recursive: true });
86
- const lockPath = path.join(cacheDirectory, 'server.lock.json');
87
-
88
- const readLock = async () => {
89
- try {
90
- const raw = await fs.readFile(lockPath, 'utf-8');
91
- return JSON.parse(raw);
92
- } catch {
93
- return null;
94
- }
95
- };
96
-
97
- const existing = await readLock();
98
- if (existing && Number.isInteger(existing.pid) && isProcessRunning(existing.pid)) {
99
- return { acquired: false, lockPath, ownerPid: existing.pid, owner: existing };
100
- }
101
-
102
- if (existing) {
103
- await fs.unlink(lockPath).catch(() => {});
104
- }
105
-
106
- const payload = {
107
- pid: process.pid,
108
- startedAt: new Date().toISOString(),
109
- workspace: workspaceDir || null,
110
- argv: process.argv.join(' '),
111
- };
112
-
113
- try {
114
- const handle = await fs.open(lockPath, 'wx');
115
- try {
116
- await handle.writeFile(JSON.stringify(payload), 'utf-8');
117
- } finally {
118
- await handle.close();
119
- }
120
- } catch (err) {
121
- if (err && err.code === 'EEXIST') {
122
- const current = await readLock();
123
- if (current && Number.isInteger(current.pid) && isProcessRunning(current.pid)) {
124
- return { acquired: false, lockPath, ownerPid: current.pid, owner: current };
125
- }
126
- await fs.unlink(lockPath).catch(() => {});
127
- return acquireWorkspaceLock({ cacheDirectory, workspaceDir });
128
- }
129
- throw err;
130
- }
131
-
132
- const cleanup = () => {
133
- try {
134
- const raw = fsSync.readFileSync(lockPath, 'utf-8');
135
- const current = JSON.parse(raw);
136
- if (current && current.pid === process.pid) {
137
- fsSync.unlinkSync(lockPath);
138
- }
139
- } catch {
140
- // ignore cleanup errors
141
- }
142
- };
143
-
144
- process.on('exit', cleanup);
145
- process.on('SIGINT', cleanup);
146
- process.on('SIGTERM', cleanup);
147
-
148
- return { acquired: true, lockPath };
149
- }
150
-
151
- export async function releaseWorkspaceLock({ cacheDirectory } = {}) {
152
- if (!cacheDirectory || isTestEnv()) {
153
- return;
154
- }
155
- const lockPath = path.join(cacheDirectory, 'server.lock.json');
156
- try {
157
- const raw = await fs.readFile(lockPath, 'utf-8');
158
- const current = JSON.parse(raw);
159
- if (current && current.pid === process.pid) {
160
- await fs.unlink(lockPath).catch(() => {});
161
- }
162
- } catch {
163
- // ignore release errors
164
- }
165
- }
1
+ import fs from 'fs/promises';
2
+ import fsSync from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+
6
+ function isTestEnv() {
7
+ return process.env.VITEST === 'true' || process.env.NODE_ENV === 'test';
8
+ }
9
+
10
+ function getPidFilePath({ pidFileName, cacheDirectory }) {
11
+ if (cacheDirectory) {
12
+ return path.join(cacheDirectory, pidFileName);
13
+ }
14
+ return path.join(os.homedir(), pidFileName);
15
+ }
16
+
17
+ export async function setupPidFile({
18
+ pidFileName = '.heuristic-mcp.pid',
19
+ cacheDirectory = null,
20
+ } = {}) {
21
+ if (isTestEnv()) {
22
+ return null;
23
+ }
24
+
25
+ const pidPath = getPidFilePath({ pidFileName, cacheDirectory });
26
+ if (cacheDirectory) {
27
+ try {
28
+ await fs.mkdir(cacheDirectory, { recursive: true });
29
+ } catch {
30
+ // ignore mkdir errors and attempt to write anyway
31
+ }
32
+ }
33
+ // Clean up stale PID file if present
34
+ try {
35
+ const raw = await fs.readFile(pidPath, 'utf-8');
36
+ const existingPid = parseInt(String(raw).trim(), 10);
37
+ if (Number.isInteger(existingPid) && !isProcessRunning(existingPid)) {
38
+ await fs.unlink(pidPath).catch(() => {});
39
+ }
40
+ } catch {
41
+ // ignore missing/invalid pid file
42
+ }
43
+ try {
44
+ await fs.writeFile(pidPath, `${process.pid}`, 'utf-8');
45
+ } catch (err) {
46
+ console.error(`[Server] Warning: Failed to write PID file: ${err.message}`);
47
+ return null;
48
+ }
49
+
50
+ const cleanup = () => {
51
+ try {
52
+ fsSync.unlinkSync(pidPath);
53
+ } catch {
54
+ // ignore
55
+ }
56
+ };
57
+
58
+ process.on('exit', cleanup);
59
+ return pidPath;
60
+ }
61
+
62
+ export function registerSignalHandlers(handler) {
63
+ process.on('SIGINT', () => handler('SIGINT'));
64
+ process.on('SIGTERM', () => handler('SIGTERM'));
65
+ }
66
+
67
+ function isProcessRunning(pid) {
68
+ try {
69
+ process.kill(pid, 0);
70
+ return true;
71
+ } catch (err) {
72
+ // On Windows, EPERM can happen even if process exists.
73
+ if (err && err.code === 'EPERM') {
74
+ return true;
75
+ }
76
+ return false;
77
+ }
78
+ }
79
+
80
+ export async function acquireWorkspaceLock({ cacheDirectory, workspaceDir = null } = {}) {
81
+ if (!cacheDirectory || isTestEnv()) {
82
+ return { acquired: true, lockPath: null };
83
+ }
84
+
85
+ await fs.mkdir(cacheDirectory, { recursive: true });
86
+ const lockPath = path.join(cacheDirectory, 'server.lock.json');
87
+
88
+ const readLock = async () => {
89
+ try {
90
+ const raw = await fs.readFile(lockPath, 'utf-8');
91
+ return JSON.parse(raw);
92
+ } catch {
93
+ return null;
94
+ }
95
+ };
96
+
97
+ const existing = await readLock();
98
+ if (existing && Number.isInteger(existing.pid) && isProcessRunning(existing.pid)) {
99
+ return { acquired: false, lockPath, ownerPid: existing.pid, owner: existing };
100
+ }
101
+
102
+ if (existing) {
103
+ await fs.unlink(lockPath).catch(() => {});
104
+ }
105
+
106
+ const payload = {
107
+ pid: process.pid,
108
+ startedAt: new Date().toISOString(),
109
+ workspace: workspaceDir || null,
110
+ argv: process.argv.join(' '),
111
+ };
112
+
113
+ try {
114
+ const handle = await fs.open(lockPath, 'wx');
115
+ try {
116
+ await handle.writeFile(JSON.stringify(payload), 'utf-8');
117
+ } finally {
118
+ await handle.close();
119
+ }
120
+ } catch (err) {
121
+ if (err && err.code === 'EEXIST') {
122
+ const current = await readLock();
123
+ if (current && Number.isInteger(current.pid) && isProcessRunning(current.pid)) {
124
+ return { acquired: false, lockPath, ownerPid: current.pid, owner: current };
125
+ }
126
+ await fs.unlink(lockPath).catch(() => {});
127
+ return acquireWorkspaceLock({ cacheDirectory, workspaceDir });
128
+ }
129
+ throw err;
130
+ }
131
+
132
+ const cleanup = () => {
133
+ try {
134
+ const raw = fsSync.readFileSync(lockPath, 'utf-8');
135
+ const current = JSON.parse(raw);
136
+ if (current && current.pid === process.pid) {
137
+ fsSync.unlinkSync(lockPath);
138
+ }
139
+ } catch {
140
+ // ignore cleanup errors
141
+ }
142
+ };
143
+
144
+ process.on('exit', cleanup);
145
+ process.on('SIGINT', cleanup);
146
+ process.on('SIGTERM', cleanup);
147
+
148
+ return { acquired: true, lockPath };
149
+ }
150
+
151
+ export async function releaseWorkspaceLock({ cacheDirectory } = {}) {
152
+ if (!cacheDirectory || isTestEnv()) {
153
+ return;
154
+ }
155
+ const lockPath = path.join(cacheDirectory, 'server.lock.json');
156
+ try {
157
+ const raw = await fs.readFile(lockPath, 'utf-8');
158
+ const current = JSON.parse(raw);
159
+ if (current && current.pid === process.pid) {
160
+ await fs.unlink(lockPath).catch(() => {});
161
+ }
162
+ } catch {
163
+ // ignore release errors
164
+ }
165
+ }