fmea-api-mcp-server 1.1.13 → 1.1.15
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/data/endpoint-lookup.json +1 -0
- package/data/search-index.oxy +1 -1
- package/dist/index.js +1 -1
- package/dist/intents.js +1 -1
- package/dist/services/search/OramaSearchService.js +98 -109
- package/dist/services/search/schema.js +4 -11
- package/dist/synonyms.js +7 -3
- package/dist/utils/ScoreCalculator.js +4 -1
- package/endpoints/v1/block-diagrams/core.json +390 -3
- package/endpoints/v1/divisions/core.json +74 -3
- package/endpoints/v1/failure-modes/core.json +46 -1
- package/endpoints/v1/knowledge-base/core.json +77 -2
- package/endpoints/v1/projects/recommendations.json +72 -1
- package/endpoints/v1/synonyms/core.json +57 -3
- package/endpoints/v1/terms/core.json +90 -3
- package/endpoints/v1/users/admin.json +89 -1
- package/endpoints/v1/users/core.json +81 -1
- package/endpoints/v2/block-diagrams/core.json +168 -2
- package/endpoints/v2/divisions/core.json +121 -1
- package/endpoints/v2/documents/core.json +213 -7
- package/endpoints/v2/excel/core.json +22 -0
- package/endpoints/v2/failure-modes/core.json +84 -0
- package/endpoints/v2/fourm/core.json +1 -1
- package/endpoints/v2/projects/core.json +698 -7
- package/endpoints/v2/system/core.json +21 -3
- package/endpoints/v2/templates/core.json +17 -2
- package/endpoints/v2/users/core.json +310 -2
- package/endpoints/v2/worksheets/core.json +148 -3
- package/endpoints/v2/worksheets/excel.json +86 -2
- package/endpoints/v2/worksheets/management.json +81 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
package/dist/intents.js
CHANGED
|
@@ -13,7 +13,7 @@ export const ACTION_KEYWORDS = {
|
|
|
13
13
|
'MONITOR': ['monitor', 'track', 'audit', 'inspect', 'history', 'logs', 'usage', 'metrics', 'stats'],
|
|
14
14
|
// CRUD
|
|
15
15
|
'create': ['create', 'add', 'new', 'make', 'generate', 'register'],
|
|
16
|
-
'update': ['update', 'edit', 'change', 'modify', 'save', 'set', 'reset', 'password', 'refresh'],
|
|
16
|
+
'update': ['update', 'edit', 'change', 'modify', 'save', 'set', 'reset', 'password', 'refresh', 'approve', 'reject'],
|
|
17
17
|
'delete': ['delete', 'remove', 'drop', 'erase'],
|
|
18
18
|
'list': ['list', 'get', 'show', 'fetch', 'all', 'view', 'files', 'status', 'check'],
|
|
19
19
|
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { create, load, search } from '@orama/orama';
|
|
2
2
|
import { pipeline } from '@xenova/transformers';
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
4
5
|
import { paginateResults, DEFAULT_PAGE_SIZE } from '../../utils/search-helper.js';
|
|
5
6
|
import { endpointSchema } from './schema.js';
|
|
6
7
|
import { RESOURCE_ALIASES } from '../../synonyms.js';
|
|
8
|
+
import { getSynonyms } from '../../synonyms.js';
|
|
7
9
|
import { ACTION_KEYWORDS, ACTION_PRIORITY } from '../../intents.js';
|
|
8
10
|
import { ScoreCalculator } from '../../utils/ScoreCalculator.js';
|
|
9
11
|
export class OramaSearchService {
|
|
@@ -14,6 +16,7 @@ export class OramaSearchService {
|
|
|
14
16
|
knownResources = ['project', 'block-diagram', 'worksheet', 'failure-mode', 'action', 'block-node', 'division', 'user', 'document', 'file', 'api-key', 'fault-tree', 'oauth'];
|
|
15
17
|
debugMode;
|
|
16
18
|
scoreCalculator;
|
|
19
|
+
lookup = new Map();
|
|
17
20
|
constructor(indexPath, debugMode = false) {
|
|
18
21
|
this.indexPath = indexPath;
|
|
19
22
|
this.debugMode = debugMode;
|
|
@@ -35,14 +38,25 @@ export class OramaSearchService {
|
|
|
35
38
|
});
|
|
36
39
|
// Load data into the DB instance
|
|
37
40
|
await load(this.db, JSON.parse(data));
|
|
38
|
-
//
|
|
41
|
+
// Load endpoint-lookup.json
|
|
42
|
+
const lookupPath = path.join(path.dirname(this.indexPath), 'endpoint-lookup.json');
|
|
43
|
+
try {
|
|
44
|
+
const lookupData = await fs.readFile(lookupPath, 'utf-8');
|
|
45
|
+
const lookupObj = JSON.parse(lookupData);
|
|
46
|
+
for (const [key, value] of Object.entries(lookupObj)) {
|
|
47
|
+
this.lookup.set(key, value);
|
|
48
|
+
}
|
|
49
|
+
console.error(`✅ OramaSearchService: Loaded ${this.lookup.size} endpoint lookups.`);
|
|
50
|
+
}
|
|
51
|
+
catch (lookupError) {
|
|
52
|
+
console.error('⚠️ OramaSearchService: endpoint-lookup.json not found, falling back to index-only mode.');
|
|
53
|
+
}
|
|
39
54
|
// Initialize model lazily or here. Here is better to fail fast.
|
|
40
55
|
this.extractor = await pipeline('feature-extraction', 'Xenova/bge-small-en-v1.5');
|
|
41
56
|
console.error('✅ OramaSearchService: Index loaded and model ready.');
|
|
42
57
|
}
|
|
43
58
|
catch (error) {
|
|
44
59
|
console.error('❌ OramaSearchService initialization failed:', error);
|
|
45
|
-
// Fallback strategy could be implemented here or handled by the caller
|
|
46
60
|
}
|
|
47
61
|
}
|
|
48
62
|
async search(query, filterMethod, filterVersion, page = 1, explicitPath) {
|
|
@@ -51,13 +65,10 @@ export class OramaSearchService {
|
|
|
51
65
|
return { results: [], meta: { total: 0, page, totalPages: 0 }, warning: 'Search service not initialized' };
|
|
52
66
|
}
|
|
53
67
|
if (!query || query.trim() === '') {
|
|
54
|
-
// Without query, we could verify behavior. Usually empty query means "list all" or "list categories"
|
|
55
|
-
// But per interface, search() expects a query string.
|
|
56
68
|
return { results: [], meta: { total: 0, page, totalPages: 0 } };
|
|
57
69
|
}
|
|
58
70
|
try {
|
|
59
71
|
// 1. Explicit Path Search (Exact/Contains) - High Priority
|
|
60
|
-
// [R16] Path Mode Detection: If query looks like a path, treat as explicit path search
|
|
61
72
|
const isPathQuery = query.startsWith('/') || query.startsWith('api/');
|
|
62
73
|
if (explicitPath || isPathQuery) {
|
|
63
74
|
const targetPath = explicitPath || query;
|
|
@@ -72,11 +83,12 @@ export class OramaSearchService {
|
|
|
72
83
|
// Sort by path length to prioritize exact match (shortest) over children
|
|
73
84
|
hits.sort((a, b) => a.document.path.length - b.document.path.length);
|
|
74
85
|
const results = hits.map((hit) => {
|
|
75
|
-
const
|
|
86
|
+
const id = hit.document.id;
|
|
87
|
+
const sourceData = this.lookup.get(id) || {};
|
|
76
88
|
const result = {
|
|
77
89
|
path: hit.document.path,
|
|
78
90
|
method: hit.document.method,
|
|
79
|
-
summary:
|
|
91
|
+
summary: sourceData.summary || '',
|
|
80
92
|
description: hit.document.description,
|
|
81
93
|
score: 1.0, // Forced high score for explicit match
|
|
82
94
|
};
|
|
@@ -100,14 +112,27 @@ export class OramaSearchService {
|
|
|
100
112
|
console.error(`[Orama] Detected Action: ${intent.action}`);
|
|
101
113
|
if (intent.resource)
|
|
102
114
|
console.error(`[Orama] Detected Resource: ${intent.resource}`);
|
|
115
|
+
// Query-time synonym expansion (targeted, not exhaustive)
|
|
103
116
|
let expandedQuery = safeQuery;
|
|
117
|
+
// Resource name expansion from intent detection
|
|
104
118
|
if (intent.resource) {
|
|
105
119
|
const resourceNameStr = intent.resource.replace(/-/g, ' ');
|
|
106
120
|
if (!expandedQuery.toLowerCase().includes(resourceNameStr)) {
|
|
107
121
|
expandedQuery += ` ${resourceNameStr}`;
|
|
108
|
-
console.error(`[Orama] Expanded Query: "${expandedQuery}"`);
|
|
109
122
|
}
|
|
110
123
|
}
|
|
124
|
+
// Limited token-level synonym expansion:
|
|
125
|
+
// Add only direct synonyms (max 3 per token) to preserve BM25 signal
|
|
126
|
+
const tokens = safeQuery.toLowerCase().split(/\s+/).filter(t => t.length >= 3);
|
|
127
|
+
const extraSynonyms = [];
|
|
128
|
+
for (const token of tokens) {
|
|
129
|
+
const syns = getSynonyms(token).filter(s => s !== token && !tokens.includes(s));
|
|
130
|
+
extraSynonyms.push(...syns.slice(0, 3));
|
|
131
|
+
}
|
|
132
|
+
if (extraSynonyms.length > 0) {
|
|
133
|
+
expandedQuery += ' ' + extraSynonyms.join(' ');
|
|
134
|
+
}
|
|
135
|
+
console.error(`[Orama] Expanded Query: "${expandedQuery}"`);
|
|
111
136
|
const output = await this.extractor(safeQuery, { pooling: 'mean', normalize: true });
|
|
112
137
|
const embedding = Array.from(output.data);
|
|
113
138
|
// 3. Build Where Clauses
|
|
@@ -116,37 +141,30 @@ export class OramaSearchService {
|
|
|
116
141
|
where.method = filterMethod;
|
|
117
142
|
if (filterVersion)
|
|
118
143
|
where.version = filterVersion;
|
|
119
|
-
// 4. Search
|
|
144
|
+
// 4. Search — description-centric with path support
|
|
120
145
|
const searchResult = await search(this.db, {
|
|
121
146
|
term: expandedQuery,
|
|
122
|
-
tolerance: 2,
|
|
123
|
-
|
|
124
|
-
|
|
147
|
+
tolerance: 2,
|
|
148
|
+
properties: ['description', 'path'],
|
|
149
|
+
boost: {
|
|
150
|
+
description: 5.0,
|
|
151
|
+
path: 10.0
|
|
152
|
+
},
|
|
125
153
|
vector: {
|
|
126
154
|
value: embedding,
|
|
127
155
|
property: 'embedding'
|
|
128
156
|
},
|
|
129
157
|
mode: 'hybrid',
|
|
130
|
-
similarity: 0,
|
|
131
|
-
boost: {
|
|
132
|
-
path: 15.0,
|
|
133
|
-
keywords: 30.0,
|
|
134
|
-
summary: 15.0,
|
|
135
|
-
description: 5.0
|
|
136
|
-
},
|
|
158
|
+
similarity: 0,
|
|
137
159
|
where,
|
|
138
|
-
limit: 1000,
|
|
160
|
+
limit: 1000,
|
|
139
161
|
offset: 0
|
|
140
162
|
});
|
|
141
|
-
// (Debug removed)
|
|
142
163
|
// 5. Threshold Filtering & Mapping with Structural Analysis
|
|
143
164
|
const results = [];
|
|
144
|
-
// Intent detection moved up.
|
|
145
165
|
for (const hit of searchResult.hits) {
|
|
146
166
|
let score = hit.score;
|
|
147
|
-
// (Debug removed)
|
|
148
167
|
// Structural Metadata from Index
|
|
149
|
-
// Note: These fields are only available if the index was built with the new schema & logic
|
|
150
168
|
const docResource = hit.document.resourceType || 'unknown';
|
|
151
169
|
const docAction = hit.document.actionType || 'other';
|
|
152
170
|
// A. & B. Intent Scoring via ScoreCalculator
|
|
@@ -155,28 +173,7 @@ export class OramaSearchService {
|
|
|
155
173
|
actionType: docAction,
|
|
156
174
|
score: score
|
|
157
175
|
});
|
|
158
|
-
// Legacy Logic Preservation (optional, maybe reduce weights)
|
|
159
|
-
// ... (Existing explicit path logic is skipped here as it's separate block)
|
|
160
|
-
// [Final Plan] Hybrid Gating (Gray Zone Filtering)
|
|
161
|
-
// Check if we should KEEP or DROP based on score brackets
|
|
162
|
-
// [Final Plan] Hybrid Gating (Gray Zone Filtering)
|
|
163
|
-
// Check if we should KEEP or DROP based on score brackets
|
|
164
|
-
// Normalized brackets:
|
|
165
|
-
// HIGH_PASS = 0.55 / 600 * 600 ??? No, wait.
|
|
166
|
-
// Old scores were raw. New scores are normalized (0.0 - 1.0).
|
|
167
|
-
// Max score ~600.
|
|
168
|
-
// Old HIGH_PASS = 0.55 (Wait, 0.55 was for normalized?? No, previously it wasn't normalized)
|
|
169
|
-
// Actually, looking at previous code, Orama scores were ~10-15 base.
|
|
170
|
-
// 0.55 seems VERY low for HIGH_PASS if boosts are +500.
|
|
171
|
-
// It seems previous threshold logic might have been based on raw Orama scores BEFORE boosts?
|
|
172
|
-
// Let's re-read: "score = hit.score" then boosts added.
|
|
173
|
-
// But "score > 10.0" check suggests intents were boosting > 10.
|
|
174
|
-
// With normalization:
|
|
175
|
-
// High Pass: Strong intent match (~500+) -> ~0.83
|
|
176
|
-
// Medium Pass: Partial match (~50+) -> ~0.1
|
|
177
|
-
// Base score (~15) -> ~0.025
|
|
178
176
|
// [Final Plan] Intent-Based Thresholding
|
|
179
|
-
// Goal: Remove noise (Action-only matches) when Intent is clear.
|
|
180
177
|
let THRESHOLD_SCORE = 0.05; // Default (Loose)
|
|
181
178
|
if (intent.resource) {
|
|
182
179
|
THRESHOLD_SCORE = 0.2;
|
|
@@ -185,39 +182,21 @@ export class OramaSearchService {
|
|
|
185
182
|
THRESHOLD_SCORE = 0.08;
|
|
186
183
|
}
|
|
187
184
|
else {
|
|
188
|
-
// [Fix] Low threshold for No Intent (Fuzzy/Typo handling)
|
|
189
|
-
// Unboosted vector matches (0.8) have normalized scores ~0.0013.
|
|
190
|
-
// 0.005 is too high. 0.001 allows strong vector matches.
|
|
191
|
-
// [Round 18] Increased to 0.01 to filter garbage like 'UPPERCASE QUERY' or 'very long...'
|
|
192
|
-
// Real keyword matches (boost=30) will be > 0.05.
|
|
193
185
|
THRESHOLD_SCORE = 0.01;
|
|
194
186
|
}
|
|
195
187
|
let keep = false;
|
|
196
|
-
// [Fix] Short queries (e.g. "x") can have high IDF/score due to noise.
|
|
197
|
-
// Force them to pass strict token matching.
|
|
198
188
|
const isShortQuery = safeQuery.length <= 2;
|
|
199
189
|
if (score >= THRESHOLD_SCORE && !isShortQuery) {
|
|
200
|
-
// Passed Threshold -> Keep
|
|
201
190
|
keep = true;
|
|
202
191
|
}
|
|
203
192
|
else if (isShortQuery) {
|
|
204
|
-
// Short Query Logic
|
|
205
|
-
const
|
|
193
|
+
// Short Query Logic: check description for token match
|
|
194
|
+
const descBag = (hit.document.description || '').toLowerCase();
|
|
206
195
|
const inputTokens = safeQuery.toLowerCase().split(/\s+/).filter(t => t.length > 0);
|
|
207
196
|
const queryTokens = new Set(inputTokens);
|
|
208
197
|
if (queryTokens.size > 0) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
// Or keep existing fuzzy logic if possible.
|
|
212
|
-
// Let's assume the previous fuzzy logic block was good. I will try to preserve it or simplify.
|
|
213
|
-
// Given tool limitations, I will rewrite the essential fuzzy check here.
|
|
214
|
-
const docTokens = new Set(keywordsBag.split(/\s+/));
|
|
215
|
-
const match = Array.from(queryTokens).some(token => {
|
|
216
|
-
if (docTokens.has(token))
|
|
217
|
-
return true;
|
|
218
|
-
// Minimal fuzzy for 3-char fallback?
|
|
219
|
-
return false;
|
|
220
|
-
});
|
|
198
|
+
const docTokens = new Set(descBag.split(/\s+/));
|
|
199
|
+
const match = Array.from(queryTokens).some(token => docTokens.has(token));
|
|
221
200
|
if (match)
|
|
222
201
|
keep = true;
|
|
223
202
|
}
|
|
@@ -227,11 +206,12 @@ export class OramaSearchService {
|
|
|
227
206
|
}
|
|
228
207
|
if (!keep)
|
|
229
208
|
continue;
|
|
230
|
-
const
|
|
209
|
+
const id = hit.document.id;
|
|
210
|
+
const sourceData = this.lookup.get(id) || {};
|
|
231
211
|
const result = {
|
|
232
212
|
path: hit.document.path,
|
|
233
213
|
method: hit.document.method,
|
|
234
|
-
summary:
|
|
214
|
+
summary: sourceData.summary || '',
|
|
235
215
|
description: hit.document.description,
|
|
236
216
|
score: score,
|
|
237
217
|
resourceType: docResource,
|
|
@@ -240,20 +220,14 @@ export class OramaSearchService {
|
|
|
240
220
|
tags: sourceData.tags,
|
|
241
221
|
warning: sourceData['x-dependency-warning'] || sourceData.warning,
|
|
242
222
|
};
|
|
243
|
-
// Include score in debug mode (already included above, but for consistency)
|
|
244
|
-
if (!this.debugMode) {
|
|
245
|
-
// In non-debug mode, exclude heavy fields
|
|
246
|
-
// (already excluded by not spreading sourceData)
|
|
247
|
-
}
|
|
248
223
|
results.push(result);
|
|
249
224
|
}
|
|
250
225
|
// Re-sort because scores changed
|
|
251
226
|
results.sort((a, b) => (b.score || 0) - (a.score || 0));
|
|
252
227
|
// 6. Pagination (Slicing)
|
|
253
|
-
const filteredTotal = results.length;
|
|
228
|
+
const filteredTotal = results.length;
|
|
254
229
|
const { results: slice, meta } = paginateResults(results, page, DEFAULT_PAGE_SIZE);
|
|
255
230
|
// 7. Warning System
|
|
256
|
-
// If query contains '/' but no explicit path provided, warn user.
|
|
257
231
|
let warning = undefined;
|
|
258
232
|
if (query.includes('/') && searchResult.count > 0 && !explicitPath && !isPathQuery) {
|
|
259
233
|
warning = "Query contains path characters. For exact path matching, use the 'path' parameter.";
|
|
@@ -262,8 +236,6 @@ export class OramaSearchService {
|
|
|
262
236
|
results: slice,
|
|
263
237
|
meta: {
|
|
264
238
|
...meta,
|
|
265
|
-
// [Fix] Report the actual filtered count, not the Orama raw candidate count.
|
|
266
|
-
// This allows clients (and tests) to know how many "relevant" items exist.
|
|
267
239
|
total: filteredTotal
|
|
268
240
|
},
|
|
269
241
|
warning
|
|
@@ -279,7 +251,35 @@ export class OramaSearchService {
|
|
|
279
251
|
if (!this.db)
|
|
280
252
|
return null;
|
|
281
253
|
try {
|
|
282
|
-
//
|
|
254
|
+
// Try lookup first (fast path)
|
|
255
|
+
if (method) {
|
|
256
|
+
const id = `${method}:${path}`;
|
|
257
|
+
const content = this.lookup.get(id);
|
|
258
|
+
if (content) {
|
|
259
|
+
const metadata = {
|
|
260
|
+
resourceType: undefined,
|
|
261
|
+
actionType: undefined,
|
|
262
|
+
score: 1.0,
|
|
263
|
+
operationId: content.operationId,
|
|
264
|
+
tags: content.tags
|
|
265
|
+
};
|
|
266
|
+
// Get resourceType/actionType from index
|
|
267
|
+
const result = await search(this.db, {
|
|
268
|
+
term: path,
|
|
269
|
+
properties: ['path'],
|
|
270
|
+
limit: 20
|
|
271
|
+
});
|
|
272
|
+
for (const hit of result.hits) {
|
|
273
|
+
if (hit.document.path === path && hit.document.method === method) {
|
|
274
|
+
metadata.resourceType = hit.document.resourceType;
|
|
275
|
+
metadata.actionType = hit.document.actionType;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return { content, metadata };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// Fallback: search index
|
|
283
283
|
const result = await search(this.db, {
|
|
284
284
|
term: path,
|
|
285
285
|
properties: ['path'],
|
|
@@ -288,11 +288,12 @@ export class OramaSearchService {
|
|
|
288
288
|
if (result.count > 0) {
|
|
289
289
|
for (const hit of result.hits) {
|
|
290
290
|
if (hit.document.path === path && (!method || hit.document.method === method)) {
|
|
291
|
-
const
|
|
291
|
+
const id = hit.document.id;
|
|
292
|
+
const content = this.lookup.get(id) || { path, method: hit.document.method, description: hit.document.description };
|
|
292
293
|
const metadata = {
|
|
293
294
|
resourceType: hit.document.resourceType,
|
|
294
295
|
actionType: hit.document.actionType,
|
|
295
|
-
score: 1.0,
|
|
296
|
+
score: 1.0,
|
|
296
297
|
operationId: content.operationId,
|
|
297
298
|
tags: content.tags
|
|
298
299
|
};
|
|
@@ -311,26 +312,14 @@ export class OramaSearchService {
|
|
|
311
312
|
await this.initPromise;
|
|
312
313
|
if (!this.db)
|
|
313
314
|
return [];
|
|
314
|
-
// Quick implementation: Since Orama facets might need schema support,
|
|
315
|
-
// and we have small data, we can just aggregate manually or return hardcoded list if known.
|
|
316
|
-
// For now, let's return a placeholder or scan top results.
|
|
317
|
-
// Ideally, we should have indexed 'category'.
|
|
318
|
-
// Let's try to return unique tags from all docs? Too expensive.
|
|
319
|
-
// Returning empty or basic list for now as per "Progressive Exploration" requires categories.
|
|
320
|
-
// Let's hack it: search everything (limit 1000) and aggregate tags/categories.
|
|
321
|
-
const result = await search(this.db, {
|
|
322
|
-
limit: 1000 // Get all
|
|
323
|
-
});
|
|
324
315
|
const categories = new Set();
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (data.tags && Array.isArray(data.tags)) {
|
|
330
|
-
data.tags.forEach((t) => categories.add(t));
|
|
316
|
+
// Use lookup map to aggregate tags
|
|
317
|
+
for (const endpoint of this.lookup.values()) {
|
|
318
|
+
if (endpoint.tags && Array.isArray(endpoint.tags)) {
|
|
319
|
+
endpoint.tags.forEach((t) => categories.add(t));
|
|
331
320
|
}
|
|
332
|
-
}
|
|
333
|
-
return Array.from(categories).map(c => ({ name: c, count: 0 }));
|
|
321
|
+
}
|
|
322
|
+
return Array.from(categories).map(c => ({ name: c, count: 0 }));
|
|
334
323
|
}
|
|
335
324
|
detectIntent(query) {
|
|
336
325
|
const lower = query.toLowerCase();
|
|
@@ -344,12 +333,8 @@ export class OramaSearchService {
|
|
|
344
333
|
}
|
|
345
334
|
}
|
|
346
335
|
// Resource Detection
|
|
347
|
-
|
|
348
|
-
let resource = undefined; // [Fix] Explicit declaration for scope visibility
|
|
336
|
+
let resource = undefined;
|
|
349
337
|
// [Round 13 Refactoring] Longest Match Strategy
|
|
350
|
-
// Issue: "project members" contains "project" (7 chars) and "member" (6 chars used in logic).
|
|
351
|
-
// If we just loop, we might hit 'project' first and stop.
|
|
352
|
-
// Solution: Sort all candidate matches by length desc, or pick the longest one.
|
|
353
338
|
const potentialResources = [];
|
|
354
339
|
// [Final Plan] Reverse Lookup from RESOURCE_ALIASES
|
|
355
340
|
for (const [resType, aliases] of Object.entries(RESOURCE_ALIASES)) {
|
|
@@ -370,9 +355,16 @@ export class OramaSearchService {
|
|
|
370
355
|
if (potentialResources.length > 0) {
|
|
371
356
|
resource = potentialResources[0].type;
|
|
372
357
|
}
|
|
358
|
+
// [Heuristic] Compound resource detection:
|
|
359
|
+
// "worksheet" + "excel" → prefer 'worksheet-excel' over either alone
|
|
360
|
+
if (lower.includes('worksheet') && lower.includes('excel')) {
|
|
361
|
+
resource = 'worksheet-excel';
|
|
362
|
+
}
|
|
363
|
+
// "worksheet" + "failure mode" → keep as 'worksheet' (not failure-mode)
|
|
364
|
+
if (lower.includes('worksheet') && (lower.includes('failure mode') || lower.includes('failuremode'))) {
|
|
365
|
+
resource = 'worksheet';
|
|
366
|
+
}
|
|
373
367
|
// [Heuristic] If resource is found but action is ambiguous, default to 'list'
|
|
374
|
-
// This prioritizes "List <Resource>" endpoints (v1) over "Get <Resource>" (v2)
|
|
375
|
-
// which helps disambiguate queries like "organization" (Noun) -> Show me the list.
|
|
376
368
|
if (!action && resource) {
|
|
377
369
|
action = 'list';
|
|
378
370
|
}
|
|
@@ -381,9 +373,6 @@ export class OramaSearchService {
|
|
|
381
373
|
resource = 'project';
|
|
382
374
|
}
|
|
383
375
|
// [Fix] Auth Actions imply 'auth' resource, overriding 'user'
|
|
384
|
-
// Example: "user login", "user sign out" -> Should target 'auth', not 'user'
|
|
385
|
-
// [Fix] Auth Actions imply 'auth' resource, overriding 'user'
|
|
386
|
-
// Example: "user login" -> 'auth'. But "third party login" -> 'oauth' (Respect explicit oauth intent)
|
|
387
376
|
if (['login', 'logout'].includes(action || '') && resource !== 'oauth') {
|
|
388
377
|
resource = 'auth';
|
|
389
378
|
}
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
export const endpointSchema = {
|
|
2
|
-
id: 'string',
|
|
2
|
+
id: 'string',
|
|
3
3
|
path: 'string',
|
|
4
4
|
method: 'string',
|
|
5
|
-
version: 'string',
|
|
6
|
-
summary: 'string',
|
|
5
|
+
version: 'string',
|
|
7
6
|
description: 'string',
|
|
8
|
-
// Vector embedding (384 dimensions for Xenova/all-MiniLM-L6-v2)
|
|
9
7
|
embedding: 'vector[384]',
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// Full JSON content stored as string to avoid schema complexity
|
|
13
|
-
jsonBlob: 'string',
|
|
14
|
-
// Structural Metadata for precise filtering
|
|
15
|
-
resourceType: 'string', // e.g. 'project', 'block-diagram'
|
|
16
|
-
actionType: 'string' // e.g. 'list', 'create', 'read', 'update', 'delete', 'other'
|
|
8
|
+
resourceType: 'string',
|
|
9
|
+
actionType: 'string',
|
|
17
10
|
};
|
package/dist/synonyms.js
CHANGED
|
@@ -14,7 +14,8 @@ export const RESOURCE_ALIASES = {
|
|
|
14
14
|
'division': ['organization', 'org', 'department', 'group', 'sector', 'unit'],
|
|
15
15
|
'knowledge': ['knowledge', 'knowledge base', 'category'], // [R17] New Resource Support + Category Mapping
|
|
16
16
|
'project': ['workspace', 'repo', 'application', 'app'], // [R13] Removed 'system' to avoid conflict
|
|
17
|
-
'system': ['system', 'health', 'status'
|
|
17
|
+
'system': ['system', 'health', 'status', 'health check', 'heartbeat', 'ping', 'deployment'],
|
|
18
|
+
'context-version': ['context version', 'context-version', 'build version', 'deployment version'], // Dedicated sub-resource
|
|
18
19
|
'auth': ['security', 'credential', 'token', 'access', 'password'], // [R17] Removed 'login' to avoid conflict with action
|
|
19
20
|
'oauth': ['third party', 'social login', 'google', 'github', 'oauth'], // [R17] Dedicated OAuth resource
|
|
20
21
|
// FMEA Domain
|
|
@@ -27,12 +28,15 @@ export const RESOURCE_ALIASES = {
|
|
|
27
28
|
'user': ['person', 'account', 'profile'], // Removed 'member' from here to avoid conflict
|
|
28
29
|
'member': ['member', 'project member', 'team'], // [R13] Distinct resource type matching indexer
|
|
29
30
|
'document': ['file', 'attachment', 'upload', 'paperwork', 'files'], // [R13] Added plural 'files'
|
|
30
|
-
'api-key': ['key', 'secret', '
|
|
31
|
+
'api-key': ['key', 'secret', 'credential'], // [R15] Cleaned: monitoring keywords (usage/metrics/logs/stats) moved to ACTION_KEYWORDS only
|
|
31
32
|
'worksheet-template': ['template', 'sheet structure'], // [R18] Keep clean - column/header expansion handled by SYNONYM_GROUPS
|
|
32
33
|
'action-status-template': ['status template', 'action status'], // [R15]
|
|
33
|
-
'join-request': ['join', 'invite', 'add user', 'member request'], // [R16]
|
|
34
|
+
'join-request': ['join', 'invite', 'add user', 'member request', 'pending', 'join request'], // [R16]
|
|
34
35
|
'excel': ['excel', 'spreadsheet', 'csv'], // [R16]
|
|
36
|
+
'worksheet-excel': ['worksheet excel', 'excel worksheet', 'worksheet export', 'worksheet download'], // Worksheet Excel import/export
|
|
35
37
|
'synonym': ['synonyms', 'synonym'], // [Fix] Key must match singular resourceType for boosting
|
|
38
|
+
'fourm': ['4m', 'four m', '4m analysis', 'man machine material environment'], // 4M Analysis
|
|
39
|
+
'term': ['term', 'terms', 'taxonomy', 'terminology', 'classification', 'glossary'], // Term tree management
|
|
36
40
|
};
|
|
37
41
|
export const SYNONYM_GROUPS = {
|
|
38
42
|
// Read / Retrieve
|
|
@@ -20,7 +20,10 @@ export class ScoreCalculator {
|
|
|
20
20
|
'knowledge': ['category'],
|
|
21
21
|
'category': ['knowledge'],
|
|
22
22
|
'oauth': ['auth'],
|
|
23
|
-
'auth': ['oauth']
|
|
23
|
+
'auth': ['oauth'],
|
|
24
|
+
'context-version': ['system'],
|
|
25
|
+
'system': ['context-version'],
|
|
26
|
+
'join-request': ['project', 'user', 'member']
|
|
24
27
|
// Note: Inverse is NOT necessarily true (searching for 'document' shouldn't show 'project' root unless necessary, but let's be strict for now)
|
|
25
28
|
};
|
|
26
29
|
/**
|