@softerist/heuristic-mcp 3.2.2 → 3.2.4

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 +3139 -3270
  8. package/features/lifecycle.js +1041 -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 +742 -762
  14. package/lib/cache-ops.js +22 -22
  15. package/lib/cache-utils.js +465 -519
  16. package/lib/cache.js +1699 -1767
  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 -492
  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 +116 -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 +133 -145
  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 +811 -591
  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,193 +1,188 @@
1
- import { env } from '@huggingface/transformers';
2
- import { createRequire } from 'module';
3
-
4
- const require = createRequire(import.meta.url);
5
- const IS_TEST_ENV = process.env.VITEST === 'true' || process.env.NODE_ENV === 'test';
6
-
7
-
8
- let ort = null;
9
- let ortLoadError = null;
10
- try {
11
- ort = await import('onnxruntime-node');
12
- } catch (e) {
13
- ortLoadError = e;
14
- }
15
-
16
- let ortVersion = null;
17
- let expectedOrtVersion = null;
18
- try {
19
- ortVersion = require('onnxruntime-node/package.json')?.version || null;
20
- } catch {
21
- ortVersion = null;
22
- }
23
- try {
24
- const transformersPkg = require('@huggingface/transformers/package.json');
25
- expectedOrtVersion =
26
- transformersPkg?.optionalDependencies?.['onnxruntime-node'] ||
27
- transformersPkg?.dependencies?.['onnxruntime-node'] ||
28
- null;
29
- } catch {
30
- expectedOrtVersion = null;
31
- }
32
-
33
-
34
- let executionProviders = null;
35
- let ONNX = null;
36
- if (!IS_TEST_ENV) {
37
- try {
38
-
39
- const dynamicImport = new Function('specifier', 'return import(specifier)');
40
- const backend = await dynamicImport('@huggingface/transformers/src/backends/onnx.js');
41
- executionProviders = backend.executionProviders;
42
- ONNX = backend.ONNX;
43
- } catch {
44
-
45
- }
46
- }
47
-
48
- let sessionThreadOptions = null;
49
- let originalCreate = null;
50
- let createPatched = false;
51
-
52
- export function getNativeOnnxStatus() {
53
- if (typeof process === 'undefined' || process?.release?.name !== 'node') {
54
- return { available: false, reason: 'not_node' };
55
- }
56
- if (ortVersion && expectedOrtVersion) {
57
- const actualParts = String(ortVersion).split('.');
58
- const expectedParts = String(expectedOrtVersion)
59
- .replace(/^[^0-9]*/, '')
60
- .split('.');
61
- const actualMajor = Number(actualParts[0] || 0);
62
- const actualMinor = Number(actualParts[1] || 0);
63
- const expectedMajor = Number(expectedParts[0] || 0);
64
- const expectedMinor = Number(expectedParts[1] || 0);
65
- if (
66
- Number.isFinite(actualMajor) &&
67
- Number.isFinite(actualMinor) &&
68
- Number.isFinite(expectedMajor) &&
69
- Number.isFinite(expectedMinor) &&
70
- (actualMajor !== expectedMajor || actualMinor !== expectedMinor)
71
- ) {
72
- return {
73
- available: false,
74
- reason: 'version_mismatch',
75
- message: `onnxruntime-node ${ortVersion} incompatible with transformers.js expectation ${expectedOrtVersion}`,
76
- };
77
- }
78
- }
79
- if (!ort?.InferenceSession) {
80
- return {
81
- available: false,
82
- reason: 'unavailable',
83
- error: ortLoadError,
84
- message: ortLoadError?.message || 'onnxruntime-node not available',
85
- };
86
- }
87
- return { available: true };
88
- }
89
-
90
- function normalizeThreadOptions(threads) {
91
- if (threads == null) return null;
92
- if (typeof threads === 'number') {
93
- const value = Number.isFinite(threads) ? Math.floor(threads) : null;
94
- if (value && value > 0) return { intraOpNumThreads: value };
95
- return null;
96
- }
97
- if (typeof threads !== 'object') return null;
98
- const intra = Number.isFinite(threads.intraOpNumThreads)
99
- ? Math.floor(threads.intraOpNumThreads)
100
- : null;
101
- const inter = Number.isFinite(threads.interOpNumThreads)
102
- ? Math.floor(threads.interOpNumThreads)
103
- : null;
104
- const normalized = {};
105
- if (intra && intra > 0) normalized.intraOpNumThreads = intra;
106
- if (inter && inter > 0) normalized.interOpNumThreads = inter;
107
- return Object.keys(normalized).length ? normalized : null;
108
- }
109
-
110
- function isOptionsCandidate(value) {
111
- if (!value || typeof value !== 'object') return false;
112
- if (ArrayBuffer.isView(value)) return false;
113
- if (value instanceof ArrayBuffer) return false;
114
- if (typeof SharedArrayBuffer !== 'undefined' && value instanceof SharedArrayBuffer) return false;
115
- return true;
116
- }
117
-
118
- function findOptionsIndex(args) {
119
- if (isOptionsCandidate(args[1])) return 1;
120
- if (typeof args[1] === 'number') {
121
- if (isOptionsCandidate(args[2])) return 2;
122
- if (typeof args[2] === 'number' && isOptionsCandidate(args[3])) return 3;
123
- }
124
- return -1;
125
- }
126
-
127
- function patchInferenceSessionCreate() {
128
- if (createPatched || !ONNX?.InferenceSession?.create) return false;
129
- originalCreate = ONNX.InferenceSession.create.bind(ONNX.InferenceSession);
130
- ONNX.InferenceSession.create = (...args) => {
131
- if (sessionThreadOptions) {
132
- const optionsIndex = findOptionsIndex(args);
133
- if (optionsIndex >= 0) {
134
- const merged = { ...args[optionsIndex] };
135
- if (merged.intraOpNumThreads == null && sessionThreadOptions.intraOpNumThreads != null) {
136
- merged.intraOpNumThreads = sessionThreadOptions.intraOpNumThreads;
137
- }
138
- if (merged.interOpNumThreads == null && sessionThreadOptions.interOpNumThreads != null) {
139
- merged.interOpNumThreads = sessionThreadOptions.interOpNumThreads;
140
- }
141
- args[optionsIndex] = merged;
142
- } else {
143
- args.push({ ...sessionThreadOptions });
144
- }
145
- }
146
- return originalCreate(...args);
147
- };
148
- createPatched = true;
149
- return true;
150
- }
151
-
152
- export function configureNativeOnnxBackend({ log, label, threads } = {}) {
153
- const status = getNativeOnnxStatus();
154
- if (!status.available) {
155
- if (log) {
156
- const msg = status.message || status.reason || 'onnxruntime-node not available';
157
- log(
158
- `${label ? `${label} ` : ''}Native ONNX backend unavailable: ${msg}. Falling back to WASM.`
159
- );
160
- }
161
- return false;
162
- }
163
-
164
- if (Array.isArray(executionProviders)) {
165
- if (executionProviders.length !== 1 || executionProviders[0] !== 'cpu') {
166
- executionProviders.length = 0;
167
- executionProviders.push('cpu');
168
- }
169
- }
170
-
171
- if (env.backends?.onnx?.wasm && typeof env.backends.onnx.wasm === 'object') {
172
- env.backends.onnx.wasm.proxy = false;
173
- }
174
-
175
- const normalizedThreads = normalizeThreadOptions(threads);
176
- if (normalizedThreads) {
177
- sessionThreadOptions = normalizedThreads;
178
- patchInferenceSessionCreate();
179
- if (log) {
180
- const intra = normalizedThreads.intraOpNumThreads ?? 'default';
181
- const inter = normalizedThreads.interOpNumThreads ?? 'default';
182
- log(
183
- `${label ? `${label} ` : ''}ONNX session threads set (intraOp=${intra}, interOp=${inter})`
184
- );
185
- }
186
- }
187
-
188
- if (log) {
189
- log(`${label ? `${label} ` : ''}ONNX backend set to onnxruntime-node (cpu)`);
190
- }
191
-
192
- return true;
193
- }
1
+ import { env } from '@huggingface/transformers';
2
+ import { createRequire } from 'module';
3
+
4
+ const require = createRequire(import.meta.url);
5
+ const IS_TEST_ENV = process.env.VITEST === 'true' || process.env.NODE_ENV === 'test';
6
+
7
+ let ort = null;
8
+ let ortLoadError = null;
9
+ try {
10
+ ort = await import('onnxruntime-node');
11
+ } catch (e) {
12
+ ortLoadError = e;
13
+ }
14
+
15
+ let ortVersion = null;
16
+ let expectedOrtVersion = null;
17
+ try {
18
+ ortVersion = require('onnxruntime-node/package.json')?.version || null;
19
+ } catch {
20
+ ortVersion = null;
21
+ }
22
+ try {
23
+ const transformersPkg = require('@huggingface/transformers/package.json');
24
+ expectedOrtVersion =
25
+ transformersPkg?.optionalDependencies?.['onnxruntime-node'] ||
26
+ transformersPkg?.dependencies?.['onnxruntime-node'] ||
27
+ null;
28
+ } catch {
29
+ expectedOrtVersion = null;
30
+ }
31
+
32
+ let executionProviders = null;
33
+ let ONNX = null;
34
+ if (!IS_TEST_ENV) {
35
+ try {
36
+ const dynamicImport = new Function('specifier', 'return import(specifier)');
37
+ const backend = await dynamicImport('@huggingface/transformers/src/backends/onnx.js');
38
+ executionProviders = backend.executionProviders;
39
+ ONNX = backend.ONNX;
40
+ } catch {}
41
+ }
42
+
43
+ let sessionThreadOptions = null;
44
+ let originalCreate = null;
45
+ let createPatched = false;
46
+
47
+ export function getNativeOnnxStatus() {
48
+ if (typeof process === 'undefined' || process?.release?.name !== 'node') {
49
+ return { available: false, reason: 'not_node' };
50
+ }
51
+ if (ortVersion && expectedOrtVersion) {
52
+ const actualParts = String(ortVersion).split('.');
53
+ const expectedParts = String(expectedOrtVersion)
54
+ .replace(/^[^0-9]*/, '')
55
+ .split('.');
56
+ const actualMajor = Number(actualParts[0] || 0);
57
+ const actualMinor = Number(actualParts[1] || 0);
58
+ const expectedMajor = Number(expectedParts[0] || 0);
59
+ const expectedMinor = Number(expectedParts[1] || 0);
60
+ if (
61
+ Number.isFinite(actualMajor) &&
62
+ Number.isFinite(actualMinor) &&
63
+ Number.isFinite(expectedMajor) &&
64
+ Number.isFinite(expectedMinor) &&
65
+ (actualMajor !== expectedMajor || actualMinor !== expectedMinor)
66
+ ) {
67
+ return {
68
+ available: false,
69
+ reason: 'version_mismatch',
70
+ message: `onnxruntime-node ${ortVersion} incompatible with transformers.js expectation ${expectedOrtVersion}`,
71
+ };
72
+ }
73
+ }
74
+ if (!ort?.InferenceSession) {
75
+ return {
76
+ available: false,
77
+ reason: 'unavailable',
78
+ error: ortLoadError,
79
+ message: ortLoadError?.message || 'onnxruntime-node not available',
80
+ };
81
+ }
82
+ return { available: true };
83
+ }
84
+
85
+ function normalizeThreadOptions(threads) {
86
+ if (threads == null) return null;
87
+ if (typeof threads === 'number') {
88
+ const value = Number.isFinite(threads) ? Math.floor(threads) : null;
89
+ if (value && value > 0) return { intraOpNumThreads: value };
90
+ return null;
91
+ }
92
+ if (typeof threads !== 'object') return null;
93
+ const intra = Number.isFinite(threads.intraOpNumThreads)
94
+ ? Math.floor(threads.intraOpNumThreads)
95
+ : null;
96
+ const inter = Number.isFinite(threads.interOpNumThreads)
97
+ ? Math.floor(threads.interOpNumThreads)
98
+ : null;
99
+ const normalized = {};
100
+ if (intra && intra > 0) normalized.intraOpNumThreads = intra;
101
+ if (inter && inter > 0) normalized.interOpNumThreads = inter;
102
+ return Object.keys(normalized).length ? normalized : null;
103
+ }
104
+
105
+ function isOptionsCandidate(value) {
106
+ if (!value || typeof value !== 'object') return false;
107
+ if (ArrayBuffer.isView(value)) return false;
108
+ if (value instanceof ArrayBuffer) return false;
109
+ if (typeof SharedArrayBuffer !== 'undefined' && value instanceof SharedArrayBuffer) return false;
110
+ return true;
111
+ }
112
+
113
+ function findOptionsIndex(args) {
114
+ if (isOptionsCandidate(args[1])) return 1;
115
+ if (typeof args[1] === 'number') {
116
+ if (isOptionsCandidate(args[2])) return 2;
117
+ if (typeof args[2] === 'number' && isOptionsCandidate(args[3])) return 3;
118
+ }
119
+ return -1;
120
+ }
121
+
122
+ function patchInferenceSessionCreate() {
123
+ if (createPatched || !ONNX?.InferenceSession?.create) return false;
124
+ originalCreate = ONNX.InferenceSession.create.bind(ONNX.InferenceSession);
125
+ ONNX.InferenceSession.create = (...args) => {
126
+ if (sessionThreadOptions) {
127
+ const optionsIndex = findOptionsIndex(args);
128
+ if (optionsIndex >= 0) {
129
+ const merged = { ...args[optionsIndex] };
130
+ if (merged.intraOpNumThreads == null && sessionThreadOptions.intraOpNumThreads != null) {
131
+ merged.intraOpNumThreads = sessionThreadOptions.intraOpNumThreads;
132
+ }
133
+ if (merged.interOpNumThreads == null && sessionThreadOptions.interOpNumThreads != null) {
134
+ merged.interOpNumThreads = sessionThreadOptions.interOpNumThreads;
135
+ }
136
+ args[optionsIndex] = merged;
137
+ } else {
138
+ args.push({ ...sessionThreadOptions });
139
+ }
140
+ }
141
+ return originalCreate(...args);
142
+ };
143
+ createPatched = true;
144
+ return true;
145
+ }
146
+
147
+ export function configureNativeOnnxBackend({ log, label, threads } = {}) {
148
+ const status = getNativeOnnxStatus();
149
+ if (!status.available) {
150
+ if (log) {
151
+ const msg = status.message || status.reason || 'onnxruntime-node not available';
152
+ log(
153
+ `${label ? `${label} ` : ''}Native ONNX backend unavailable: ${msg}. Falling back to WASM.`
154
+ );
155
+ }
156
+ return false;
157
+ }
158
+
159
+ if (Array.isArray(executionProviders)) {
160
+ if (executionProviders.length !== 1 || executionProviders[0] !== 'cpu') {
161
+ executionProviders.length = 0;
162
+ executionProviders.push('cpu');
163
+ }
164
+ }
165
+
166
+ if (env.backends?.onnx?.wasm && typeof env.backends.onnx.wasm === 'object') {
167
+ env.backends.onnx.wasm.proxy = false;
168
+ }
169
+
170
+ const normalizedThreads = normalizeThreadOptions(threads);
171
+ if (normalizedThreads) {
172
+ sessionThreadOptions = normalizedThreads;
173
+ patchInferenceSessionCreate();
174
+ if (log) {
175
+ const intra = normalizedThreads.intraOpNumThreads ?? 'default';
176
+ const inter = normalizedThreads.interOpNumThreads ?? 'default';
177
+ log(
178
+ `${label ? `${label} ` : ''}ONNX session threads set (intraOp=${intra}, interOp=${inter})`
179
+ );
180
+ }
181
+ }
182
+
183
+ if (log) {
184
+ log(`${label ? `${label} ` : ''}ONNX backend set to onnxruntime-node (cpu)`);
185
+ }
186
+
187
+ return true;
188
+ }
package/lib/path-utils.js CHANGED
@@ -1,23 +1,18 @@
1
-
2
-
3
- import path from 'path';
4
-
5
-
6
- export function normalizePath(value) {
7
- if (typeof value !== 'string') return '';
8
- const normalized = value.replace(/\\/g, '/');
9
- return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
10
- }
11
-
12
-
13
- export function isPathInside(basePath, targetPath) {
14
- const normalizedBase = normalizePath(basePath);
15
- const normalizedTarget = normalizePath(targetPath);
16
- const relative = path.relative(normalizedBase, normalizedTarget);
17
- return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
18
- }
19
-
20
-
21
- export function normalizePathKey(filePath) {
22
- return normalizePath(filePath);
23
- }
1
+ import path from 'path';
2
+
3
+ export function normalizePath(value) {
4
+ if (typeof value !== 'string') return '';
5
+ const normalized = value.replace(/\\/g, '/');
6
+ return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
7
+ }
8
+
9
+ export function isPathInside(basePath, targetPath) {
10
+ const normalizedBase = normalizePath(basePath);
11
+ const normalizedTarget = normalizePath(targetPath);
12
+ const relative = path.relative(normalizedBase, normalizedTarget);
13
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
14
+ }
15
+
16
+ export function normalizePathKey(filePath) {
17
+ return normalizePath(filePath);
18
+ }
@@ -1,84 +1,82 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
- import { FILE_TYPE_MAP, IGNORE_PATTERNS, SKIP_DIRECTORIES } from './ignore-patterns.js';
4
-
5
- export class ProjectDetector {
6
- constructor(searchDirectory) {
7
- this.searchDirectory = searchDirectory;
8
- this.detectedTypes = new Set();
9
- }
10
-
11
- async detectProjectTypes(options = {}) {
12
- const maxDepth = typeof options.maxDepth === 'number' ? options.maxDepth : 2;
13
- const startDepth = typeof options.startDepth === 'number' ? options.startDepth : 0;
14
- const markerFiles = Object.keys(FILE_TYPE_MAP);
15
- const discoveredTypes = new Map();
16
-
17
- const checkDir = async (dir, depth) => {
18
- if (depth > maxDepth) return;
19
-
20
- const items = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
21
- const itemNames = items.map((i) => i.name);
22
- const itemSet = new Set(itemNames);
23
-
24
- for (const marker of markerFiles) {
25
- let found = false;
26
- if (marker.includes('*')) {
27
- const regex = new RegExp('^' + marker.replace('*', '.*') + '$');
28
- found = itemNames.some((file) => regex.test(file));
29
- } else {
30
- found = itemSet.has(marker);
31
- }
32
-
33
- if (found) {
34
- const type = FILE_TYPE_MAP[marker];
35
- if (!discoveredTypes.has(type)) {
36
- discoveredTypes.set(type, path.relative(this.searchDirectory, path.join(dir, marker)));
37
- }
38
- }
39
- }
40
-
41
-
42
- if (depth < maxDepth) {
43
- for (const item of items) {
44
- if (item.isDirectory()) {
45
- const name = item.name;
46
- if (name.startsWith('.') || SKIP_DIRECTORIES.includes(name)) {
47
- continue;
48
- }
49
- await checkDir(path.join(dir, name), depth + 1);
50
- }
51
- }
52
- }
53
- };
54
-
55
- await checkDir(this.searchDirectory, startDepth);
56
-
57
- for (const [type, marker] of discoveredTypes) {
58
- this.detectedTypes.add(type);
59
- console.info(`[Detector] Detected ${type} project (${marker})`);
60
- }
61
-
62
- return Array.from(this.detectedTypes);
63
- }
64
-
65
- getSmartIgnorePatterns() {
66
- const patterns = [...IGNORE_PATTERNS.common];
67
-
68
- for (const type of this.detectedTypes) {
69
- if (IGNORE_PATTERNS[type]) {
70
- patterns.push(...IGNORE_PATTERNS[type]);
71
- }
72
- }
73
-
74
-
75
- return [...new Set(patterns)];
76
- }
77
-
78
- getSummary() {
79
- return {
80
- detectedTypes: Array.from(this.detectedTypes),
81
- patternCount: this.getSmartIgnorePatterns().length,
82
- };
83
- }
84
- }
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { FILE_TYPE_MAP, IGNORE_PATTERNS, SKIP_DIRECTORIES } from './ignore-patterns.js';
4
+
5
+ export class ProjectDetector {
6
+ constructor(searchDirectory) {
7
+ this.searchDirectory = searchDirectory;
8
+ this.detectedTypes = new Set();
9
+ }
10
+
11
+ async detectProjectTypes(options = {}) {
12
+ const maxDepth = typeof options.maxDepth === 'number' ? options.maxDepth : 2;
13
+ const startDepth = typeof options.startDepth === 'number' ? options.startDepth : 0;
14
+ const markerFiles = Object.keys(FILE_TYPE_MAP);
15
+ const discoveredTypes = new Map();
16
+
17
+ const checkDir = async (dir, depth) => {
18
+ if (depth > maxDepth) return;
19
+
20
+ const items = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
21
+ const itemNames = items.map((i) => i.name);
22
+ const itemSet = new Set(itemNames);
23
+
24
+ for (const marker of markerFiles) {
25
+ let found = false;
26
+ if (marker.includes('*')) {
27
+ const regex = new RegExp('^' + marker.replace('*', '.*') + '$');
28
+ found = itemNames.some((file) => regex.test(file));
29
+ } else {
30
+ found = itemSet.has(marker);
31
+ }
32
+
33
+ if (found) {
34
+ const type = FILE_TYPE_MAP[marker];
35
+ if (!discoveredTypes.has(type)) {
36
+ discoveredTypes.set(type, path.relative(this.searchDirectory, path.join(dir, marker)));
37
+ }
38
+ }
39
+ }
40
+
41
+ if (depth < maxDepth) {
42
+ for (const item of items) {
43
+ if (item.isDirectory()) {
44
+ const name = item.name;
45
+ if (name.startsWith('.') || SKIP_DIRECTORIES.includes(name)) {
46
+ continue;
47
+ }
48
+ await checkDir(path.join(dir, name), depth + 1);
49
+ }
50
+ }
51
+ }
52
+ };
53
+
54
+ await checkDir(this.searchDirectory, startDepth);
55
+
56
+ for (const [type, marker] of discoveredTypes) {
57
+ this.detectedTypes.add(type);
58
+ console.info(`[Detector] Detected ${type} project (${marker})`);
59
+ }
60
+
61
+ return Array.from(this.detectedTypes);
62
+ }
63
+
64
+ getSmartIgnorePatterns() {
65
+ const patterns = [...IGNORE_PATTERNS.common];
66
+
67
+ for (const type of this.detectedTypes) {
68
+ if (IGNORE_PATTERNS[type]) {
69
+ patterns.push(...IGNORE_PATTERNS[type]);
70
+ }
71
+ }
72
+
73
+ return [...new Set(patterns)];
74
+ }
75
+
76
+ getSummary() {
77
+ return {
78
+ detectedTypes: Array.from(this.detectedTypes),
79
+ patternCount: this.getSmartIgnorePatterns().length,
80
+ };
81
+ }
82
+ }