@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.
- 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 -75
- 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/lib/call-graph.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Works across multiple languages without external dependencies.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import path from
|
|
8
|
+
import path from 'path';
|
|
9
9
|
|
|
10
10
|
// Language-specific patterns for function/method definitions
|
|
11
11
|
const DEFINITION_PATTERNS = {
|
|
@@ -19,30 +19,30 @@ const DEFINITION_PATTERNS = {
|
|
|
19
19
|
// method definitions: name() { or async name() {
|
|
20
20
|
/^\s*(?:async\s+)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\([^)]*\)\s*\{/gm,
|
|
21
21
|
// object method shorthand: name() { inside object
|
|
22
|
-
/([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\([^)]*\)\s*\{/g
|
|
22
|
+
/([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\([^)]*\)\s*\{/g,
|
|
23
23
|
],
|
|
24
24
|
python: [
|
|
25
25
|
// def name():
|
|
26
26
|
/def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g,
|
|
27
27
|
// class Name:
|
|
28
|
-
/class\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*[:(]/g
|
|
28
|
+
/class\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*[:(]/g,
|
|
29
29
|
],
|
|
30
30
|
go: [
|
|
31
31
|
// func name() or func (r Receiver) name()
|
|
32
|
-
/func\s+(?:\([^)]*\)\s+)?([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g
|
|
32
|
+
/func\s+(?:\([^)]*\)\s+)?([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g,
|
|
33
33
|
],
|
|
34
34
|
rust: [
|
|
35
35
|
// fn name()
|
|
36
36
|
/fn\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*[<(]/g,
|
|
37
37
|
// impl Name
|
|
38
|
-
/impl(?:\s*<[^>]*>)?\s+([a-zA-Z_][a-zA-Z0-9_]*)/g
|
|
38
|
+
/impl(?:\s*<[^>]*>)?\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
|
|
39
39
|
],
|
|
40
40
|
java: [
|
|
41
41
|
// public void name() or private static String name()
|
|
42
42
|
/(?:public|private|protected)?\s*(?:static)?\s*(?:\w+)\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g,
|
|
43
43
|
// class Name
|
|
44
|
-
/class\s+([a-zA-Z_][a-zA-Z0-9_]*)/g
|
|
45
|
-
]
|
|
44
|
+
/class\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
|
|
45
|
+
],
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
// Pattern for function calls (language-agnostic, catches most cases)
|
|
@@ -51,19 +51,117 @@ const CALL_PATTERN = /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
|
|
|
51
51
|
// Common built-ins to exclude from call detection (all lowercase for case-insensitive matching)
|
|
52
52
|
const BUILTIN_EXCLUSIONS = new Set([
|
|
53
53
|
// JavaScript
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
'if',
|
|
55
|
+
'for',
|
|
56
|
+
'while',
|
|
57
|
+
'switch',
|
|
58
|
+
'catch',
|
|
59
|
+
'function',
|
|
60
|
+
'async',
|
|
61
|
+
'await',
|
|
62
|
+
'return',
|
|
63
|
+
'throw',
|
|
64
|
+
'new',
|
|
65
|
+
'typeof',
|
|
66
|
+
'instanceof',
|
|
67
|
+
'delete',
|
|
68
|
+
'void',
|
|
69
|
+
'console',
|
|
70
|
+
'require',
|
|
71
|
+
'import',
|
|
72
|
+
'export',
|
|
73
|
+
'super',
|
|
74
|
+
'this',
|
|
57
75
|
// Common functions that aren't meaningful for call graphs
|
|
58
|
-
|
|
59
|
-
|
|
76
|
+
'parseint',
|
|
77
|
+
'parsefloat',
|
|
78
|
+
'string',
|
|
79
|
+
'number',
|
|
80
|
+
'boolean',
|
|
81
|
+
'array',
|
|
82
|
+
'object',
|
|
83
|
+
'map',
|
|
84
|
+
'set',
|
|
85
|
+
'promise',
|
|
86
|
+
'error',
|
|
87
|
+
'json',
|
|
88
|
+
'math',
|
|
89
|
+
'date',
|
|
90
|
+
'regexp',
|
|
60
91
|
// Python
|
|
61
|
-
|
|
62
|
-
|
|
92
|
+
'def',
|
|
93
|
+
'class',
|
|
94
|
+
'print',
|
|
95
|
+
'len',
|
|
96
|
+
'range',
|
|
97
|
+
'str',
|
|
98
|
+
'int',
|
|
99
|
+
'float',
|
|
100
|
+
'list',
|
|
101
|
+
'dict',
|
|
102
|
+
'tuple',
|
|
103
|
+
'bool',
|
|
104
|
+
'type',
|
|
105
|
+
'isinstance',
|
|
106
|
+
'hasattr',
|
|
107
|
+
'getattr',
|
|
108
|
+
'setattr',
|
|
63
109
|
// Go
|
|
64
|
-
|
|
110
|
+
'func',
|
|
111
|
+
'make',
|
|
112
|
+
'append',
|
|
113
|
+
'cap',
|
|
114
|
+
'panic',
|
|
115
|
+
'recover',
|
|
65
116
|
// Control flow that looks like function calls
|
|
66
|
-
|
|
117
|
+
'else',
|
|
118
|
+
'try',
|
|
119
|
+
'finally',
|
|
120
|
+
'with',
|
|
121
|
+
'assert',
|
|
122
|
+
'raise',
|
|
123
|
+
'yield',
|
|
124
|
+
// Test frameworks
|
|
125
|
+
'describe',
|
|
126
|
+
'it',
|
|
127
|
+
'test',
|
|
128
|
+
'expect',
|
|
129
|
+
'beforeeach',
|
|
130
|
+
'aftereach',
|
|
131
|
+
'beforeall',
|
|
132
|
+
'afterall',
|
|
133
|
+
// Common prototypes / methods (too noisy)
|
|
134
|
+
'match',
|
|
135
|
+
'exec',
|
|
136
|
+
'replace',
|
|
137
|
+
'split',
|
|
138
|
+
'join',
|
|
139
|
+
'slice',
|
|
140
|
+
'splice',
|
|
141
|
+
'push',
|
|
142
|
+
'pop',
|
|
143
|
+
'shift',
|
|
144
|
+
'unshift',
|
|
145
|
+
'includes',
|
|
146
|
+
'indexof',
|
|
147
|
+
'foreach',
|
|
148
|
+
'filter',
|
|
149
|
+
'reduce',
|
|
150
|
+
'find',
|
|
151
|
+
'some',
|
|
152
|
+
'every',
|
|
153
|
+
'sort',
|
|
154
|
+
'keys',
|
|
155
|
+
'values',
|
|
156
|
+
'entries',
|
|
157
|
+
'from',
|
|
158
|
+
'then',
|
|
159
|
+
'catch',
|
|
160
|
+
'finally',
|
|
161
|
+
'all',
|
|
162
|
+
'race',
|
|
163
|
+
'resolve',
|
|
164
|
+
'reject',
|
|
67
165
|
]);
|
|
68
166
|
|
|
69
167
|
/**
|
|
@@ -72,29 +170,34 @@ const BUILTIN_EXCLUSIONS = new Set([
|
|
|
72
170
|
function detectLanguage(file) {
|
|
73
171
|
const ext = path.extname(file).toLowerCase();
|
|
74
172
|
const langMap = {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
173
|
+
'.js': 'javascript',
|
|
174
|
+
'.jsx': 'javascript',
|
|
175
|
+
'.ts': 'javascript',
|
|
176
|
+
'.tsx': 'javascript',
|
|
177
|
+
'.mjs': 'javascript',
|
|
178
|
+
'.cjs': 'javascript',
|
|
179
|
+
'.py': 'python',
|
|
180
|
+
'.pyw': 'python',
|
|
181
|
+
'.go': 'go',
|
|
182
|
+
'.rs': 'rust',
|
|
183
|
+
'.java': 'java',
|
|
184
|
+
'.kt': 'java',
|
|
185
|
+
'.scala': 'java',
|
|
88
186
|
};
|
|
89
|
-
|
|
187
|
+
if (langMap[ext]) {
|
|
188
|
+
return langMap[ext];
|
|
189
|
+
} else {
|
|
190
|
+
return 'javascript'; // Default to JS patterns
|
|
191
|
+
}
|
|
90
192
|
}
|
|
91
193
|
|
|
92
194
|
/**
|
|
93
195
|
* Extract function/class definitions from content
|
|
196
|
+
* Exported for testing; treat as internal helper.
|
|
94
197
|
*/
|
|
95
198
|
export function extractDefinitions(content, file) {
|
|
96
199
|
const language = detectLanguage(file);
|
|
97
|
-
const patterns = DEFINITION_PATTERNS[language]
|
|
200
|
+
const patterns = DEFINITION_PATTERNS[language];
|
|
98
201
|
const definitions = new Set();
|
|
99
202
|
|
|
100
203
|
for (const pattern of patterns) {
|
|
@@ -114,6 +217,7 @@ export function extractDefinitions(content, file) {
|
|
|
114
217
|
|
|
115
218
|
/**
|
|
116
219
|
* Extract function calls from content
|
|
220
|
+
* Exported for testing; treat as internal helper.
|
|
117
221
|
*/
|
|
118
222
|
export function extractCalls(content, file) {
|
|
119
223
|
const calls = new Set();
|
|
@@ -140,23 +244,23 @@ function removeStringsAndComments(content, file) {
|
|
|
140
244
|
const ext = path.extname(file).toLowerCase();
|
|
141
245
|
|
|
142
246
|
// Remove single-line comments
|
|
143
|
-
let cleaned = content.replace(/\/\/.*$/gm,
|
|
247
|
+
let cleaned = content.replace(/\/\/.*$/gm, '');
|
|
144
248
|
|
|
145
249
|
// Remove multi-line comments
|
|
146
|
-
cleaned = cleaned.replace(/\/\*[\s\S]*?\*\//g,
|
|
250
|
+
cleaned = cleaned.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
147
251
|
|
|
148
252
|
// Remove Python comments
|
|
149
|
-
if (ext ===
|
|
150
|
-
cleaned = cleaned.replace(/#.*$/gm,
|
|
253
|
+
if (ext === '.py' || ext === '.pyw') {
|
|
254
|
+
cleaned = cleaned.replace(/#.*$/gm, '');
|
|
151
255
|
// Remove triple-quoted strings (docstrings)
|
|
152
|
-
cleaned = cleaned.replace(/"""[\s\S]*?"""/g,
|
|
153
|
-
cleaned = cleaned.replace(/'''[\s\S]*?'''/g,
|
|
256
|
+
cleaned = cleaned.replace(/"""[\s\S]*?"""/g, '');
|
|
257
|
+
cleaned = cleaned.replace(/'''[\s\S]*?'''/g, '');
|
|
154
258
|
}
|
|
155
259
|
|
|
156
260
|
// Remove string literals (simplified - handles most cases)
|
|
157
261
|
cleaned = cleaned.replace(/"(?:[^"\\]|\\.)*"/g, '""');
|
|
158
262
|
cleaned = cleaned.replace(/'(?:[^'\\]|\\.)*'/g, "''");
|
|
159
|
-
cleaned = cleaned.replace(/`(?:[^`\\]|\\.)*`/g,
|
|
263
|
+
cleaned = cleaned.replace(/`(?:[^`\\]|\\.)*`/g, '``');
|
|
160
264
|
|
|
161
265
|
return cleaned;
|
|
162
266
|
}
|
|
@@ -170,11 +274,11 @@ export function extractCallData(content, file) {
|
|
|
170
274
|
|
|
171
275
|
// Remove self-references (calls to functions defined in same file)
|
|
172
276
|
const definitionSet = new Set(definitions);
|
|
173
|
-
const externalCalls = calls.filter(c => !definitionSet.has(c));
|
|
277
|
+
const externalCalls = calls.filter((c) => !definitionSet.has(c));
|
|
174
278
|
|
|
175
279
|
return {
|
|
176
280
|
definitions,
|
|
177
|
-
calls: externalCalls
|
|
281
|
+
calls: externalCalls,
|
|
178
282
|
};
|
|
179
283
|
}
|
|
180
284
|
|
|
@@ -182,7 +286,7 @@ export function extractCallData(content, file) {
|
|
|
182
286
|
* Build a call graph from file data
|
|
183
287
|
*/
|
|
184
288
|
export function buildCallGraph(fileCallData) {
|
|
185
|
-
const defines = new Map();
|
|
289
|
+
const defines = new Map(); // symbol -> files that define it
|
|
186
290
|
const calledBy = new Map(); // symbol -> files that call it
|
|
187
291
|
const fileCalls = new Map(); // file -> symbols it calls
|
|
188
292
|
|
|
@@ -253,21 +357,32 @@ export function getRelatedFiles(callGraph, symbols, maxHops = 1) {
|
|
|
253
357
|
return related;
|
|
254
358
|
}
|
|
255
359
|
|
|
360
|
+
// Patterns for extracting symbols from content
|
|
361
|
+
const SYMBOL_PATTERNS = [
|
|
362
|
+
// function name()
|
|
363
|
+
/function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g,
|
|
364
|
+
// class Name
|
|
365
|
+
/class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g,
|
|
366
|
+
// const/let/var name = ...
|
|
367
|
+
/(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=/g,
|
|
368
|
+
// Python def/class
|
|
369
|
+
/def\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
|
|
370
|
+
/class\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
|
|
371
|
+
// Go func
|
|
372
|
+
/func\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
|
|
373
|
+
// Rust fn
|
|
374
|
+
/fn\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
|
|
375
|
+
// Java/C# methods (simplified)
|
|
376
|
+
/(?:public|private|protected|static)\s+\w+\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g
|
|
377
|
+
];
|
|
378
|
+
|
|
256
379
|
/**
|
|
257
380
|
* Extract symbols (function/class names) from search results
|
|
258
381
|
*/
|
|
259
382
|
export function extractSymbolsFromContent(content) {
|
|
260
383
|
const symbols = new Set();
|
|
261
384
|
|
|
262
|
-
|
|
263
|
-
const patterns = [
|
|
264
|
-
/function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g,
|
|
265
|
-
/class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g,
|
|
266
|
-
/def\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
|
|
267
|
-
/(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=/g
|
|
268
|
-
];
|
|
269
|
-
|
|
270
|
-
for (const pattern of patterns) {
|
|
385
|
+
for (const pattern of SYMBOL_PATTERNS) {
|
|
271
386
|
pattern.lastIndex = 0;
|
|
272
387
|
let match;
|
|
273
388
|
while ((match = pattern.exec(content)) !== null) {
|
package/lib/cli.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
export const DEFAULT_LOG_TAIL_LINES = 200;
|
|
2
|
+
|
|
3
|
+
export function printHelp(defaultTailLines = DEFAULT_LOG_TAIL_LINES) {
|
|
4
|
+
console.info(`Heuristic MCP Server
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
heuristic-mcp [options]
|
|
8
|
+
|
|
9
|
+
Options:
|
|
10
|
+
--status Show server and cache status
|
|
11
|
+
--fix With --status, remove stale cache directories
|
|
12
|
+
--clear-cache Remove cache for current workspace (and stale global caches)
|
|
13
|
+
--logs Tail server logs (defaults to last 200 lines, follows)
|
|
14
|
+
--tail <lines> Lines to show with --logs (default: ${defaultTailLines})
|
|
15
|
+
--no-follow Do not follow log output with --logs
|
|
16
|
+
--start Ensure IDE config is registered (does not start server)
|
|
17
|
+
--stop Stop running server instances
|
|
18
|
+
--register [ide] Register MCP server with IDE (antigravity|cursor|"Claude Desktop")
|
|
19
|
+
--workspace <path> Workspace path (used by IDE launch / log viewer)
|
|
20
|
+
--version, -v Show version
|
|
21
|
+
--help, -h Show this help
|
|
22
|
+
`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function parseWorkspaceDir(args) {
|
|
26
|
+
const workspaceIndex = args.findIndex((arg) => arg.startsWith('--workspace'));
|
|
27
|
+
if (workspaceIndex === -1) return null;
|
|
28
|
+
|
|
29
|
+
const arg = args[workspaceIndex];
|
|
30
|
+
let rawWorkspace = null;
|
|
31
|
+
|
|
32
|
+
if (arg.includes('=')) {
|
|
33
|
+
rawWorkspace = arg.split('=')[1];
|
|
34
|
+
} else if (workspaceIndex + 1 < args.length) {
|
|
35
|
+
rawWorkspace = args[workspaceIndex + 1];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check if IDE variable wasn't expanded (contains ${})
|
|
39
|
+
if (rawWorkspace && rawWorkspace.includes('${')) {
|
|
40
|
+
console.error(
|
|
41
|
+
`[Server] IDE variable not expanded: ${rawWorkspace}, falling back to auto-detected workspace`
|
|
42
|
+
);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return rawWorkspace || null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function collectUnknownFlags(rawArgs, knownFlags, flagsWithValue) {
|
|
50
|
+
const unknownFlags = [];
|
|
51
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
52
|
+
const arg = rawArgs[i];
|
|
53
|
+
if (flagsWithValue.has(arg)) {
|
|
54
|
+
if (arg.includes('=')) continue;
|
|
55
|
+
const next = rawArgs[i + 1];
|
|
56
|
+
if (next && !next.startsWith('-')) {
|
|
57
|
+
i += 1;
|
|
58
|
+
}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (arg.startsWith('-') && !knownFlags.has(arg) && !arg.startsWith('--workspace=')) {
|
|
62
|
+
unknownFlags.push(arg);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return unknownFlags;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function parseArgs(argv = process.argv) {
|
|
69
|
+
const args = argv.slice(2);
|
|
70
|
+
const rawArgs = [...args];
|
|
71
|
+
|
|
72
|
+
const wantsVersion = args.includes('--version') || args.includes('-v');
|
|
73
|
+
const wantsHelp = args.includes('--help') || args.includes('-h');
|
|
74
|
+
const wantsStatus = args.includes('--status');
|
|
75
|
+
const wantsClearCache = args.includes('--clear-cache');
|
|
76
|
+
const wantsLogs = args.includes('--logs');
|
|
77
|
+
const wantsStart = args.includes('--start');
|
|
78
|
+
const wantsStop = args.includes('--stop');
|
|
79
|
+
const wantsRegister = args.includes('--register');
|
|
80
|
+
const wantsFix = args.includes('--fix');
|
|
81
|
+
const wantsNoFollow = args.includes('--no-follow');
|
|
82
|
+
|
|
83
|
+
const isServerMode = !(
|
|
84
|
+
wantsStatus ||
|
|
85
|
+
wantsClearCache ||
|
|
86
|
+
wantsLogs ||
|
|
87
|
+
wantsStart ||
|
|
88
|
+
wantsStop ||
|
|
89
|
+
wantsRegister ||
|
|
90
|
+
wantsHelp ||
|
|
91
|
+
wantsVersion
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const workspaceDir = parseWorkspaceDir(args);
|
|
95
|
+
|
|
96
|
+
let tailLines = DEFAULT_LOG_TAIL_LINES;
|
|
97
|
+
if (wantsLogs) {
|
|
98
|
+
const tailIndex = args.indexOf('--tail');
|
|
99
|
+
if (tailIndex !== -1 && args[tailIndex + 1]) {
|
|
100
|
+
const parsed = parseInt(args[tailIndex + 1], 10);
|
|
101
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
102
|
+
tailLines = parsed;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let registerFilter = null;
|
|
108
|
+
if (wantsRegister) {
|
|
109
|
+
const filterIndex = args.indexOf('--register');
|
|
110
|
+
registerFilter =
|
|
111
|
+
args[filterIndex + 1] && !args[filterIndex + 1].startsWith('-')
|
|
112
|
+
? args[filterIndex + 1]
|
|
113
|
+
: null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const knownFlags = new Set([
|
|
117
|
+
'--status',
|
|
118
|
+
'--fix',
|
|
119
|
+
'--clear-cache',
|
|
120
|
+
'--logs',
|
|
121
|
+
'--tail',
|
|
122
|
+
'--no-follow',
|
|
123
|
+
'--start',
|
|
124
|
+
'--stop',
|
|
125
|
+
'--register',
|
|
126
|
+
'--workspace',
|
|
127
|
+
'--version',
|
|
128
|
+
'-v',
|
|
129
|
+
'--help',
|
|
130
|
+
'-h',
|
|
131
|
+
]);
|
|
132
|
+
const flagsWithValue = new Set(['--tail', '--workspace', '--register']);
|
|
133
|
+
const unknownFlags = collectUnknownFlags(rawArgs, knownFlags, flagsWithValue);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
args,
|
|
137
|
+
rawArgs,
|
|
138
|
+
isServerMode,
|
|
139
|
+
workspaceDir,
|
|
140
|
+
wantsVersion,
|
|
141
|
+
wantsHelp,
|
|
142
|
+
wantsStatus,
|
|
143
|
+
wantsClearCache,
|
|
144
|
+
wantsLogs,
|
|
145
|
+
wantsStart,
|
|
146
|
+
wantsStop,
|
|
147
|
+
wantsRegister,
|
|
148
|
+
wantsFix,
|
|
149
|
+
wantsNoFollow,
|
|
150
|
+
tailLines,
|
|
151
|
+
registerFilter,
|
|
152
|
+
unknownFlags,
|
|
153
|
+
};
|
|
154
|
+
}
|