@futdevpro/fdp-agent-memory 0.1.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/README.md +345 -0
- package/build/package.json +96 -0
- package/build/src/_assets/mcp-client-config/README.md +29 -0
- package/build/src/_assets/mcp-client-config/claude_desktop_config.json +15 -0
- package/build/src/_assets/mcp-client-config/mcp.json +15 -0
- package/build/src/_collections/config-catalog.const.js +180 -0
- package/build/src/_collections/config-error-codes.const.js +30 -0
- package/build/src/_collections/config-presets.const.js +25 -0
- package/build/src/_collections/error-banners.const.js +100 -0
- package/build/src/_collections/error-codes.const.js +150 -0
- package/build/src/_collections/fam-db-models.const.js +37 -0
- package/build/src/_collections/fam-entry-bootstrap.util.js +80 -0
- package/build/src/_collections/fam-error-context.util.js +90 -0
- package/build/src/_collections/fam-error-factory.util.js +64 -0
- package/build/src/_enums/fam-config-level.type-enum.js +15 -0
- package/build/src/_enums/fam-table.type-enum.js +20 -0
- package/build/src/_integration-tests/_helpers/fam-integration-test-setup.util.js +105 -0
- package/build/src/_models/data-models/fam-codebase.data-model.js +51 -0
- package/build/src/_models/data-models/fam-coding-patterns.data-model.js +58 -0
- package/build/src/_models/data-models/fam-config.data-model.js +68 -0
- package/build/src/_models/data-models/fam-documents.data-model.js +53 -0
- package/build/src/_models/data-models/fam-entry-base-properties.const.js +43 -0
- package/build/src/_models/data-models/fam-entry.data-model.js +81 -0
- package/build/src/_models/data-models/fam-error.data-model.js +88 -0
- package/build/src/_models/data-models/fam-ingest-run.data-model.js +74 -0
- package/build/src/_models/data-models/fam-knowledge.data-model.js +48 -0
- package/build/src/_models/data-models/fam-memory.data-model.js +55 -0
- package/build/src/_models/data-models/fam-reference.data-model.js +67 -0
- package/build/src/_models/data-models/fam-rules.data-model.js +51 -0
- package/build/src/_models/data-models/fam-scope.data-model.js +52 -0
- package/build/src/_models/interfaces/fam-common.interface.js +23 -0
- package/build/src/_models/interfaces/fam-config.interface.js +2 -0
- package/build/src/_models/interfaces/fam-error.interface.js +2 -0
- package/build/src/_modules/embedding/_collections/fam-embedding-pricing.const.js +22 -0
- package/build/src/_modules/embedding/_collections/fam-store-registry.const.js +63 -0
- package/build/src/_modules/embedding/_models/interfaces/fam-embedding-cost.interface.js +10 -0
- package/build/src/_modules/embedding/_models/interfaces/fam-embedding-provider.interface.js +2 -0
- package/build/src/_modules/embedding/_models/interfaces/fam-resolved-provider.interface.js +2 -0
- package/build/src/_modules/embedding/_services/fam-embedding-bootstrap.control-service.js +52 -0
- package/build/src/_modules/embedding/_services/fam-embedding-cost.control-service.js +175 -0
- package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +202 -0
- package/build/src/_modules/embedding/_services/fam-embedding-preset.control-service.js +66 -0
- package/build/src/_modules/embedding/_services/fam-embedding.control-service.js +253 -0
- package/build/src/_modules/embedding/_services/fam-entry.data-service.js +64 -0
- package/build/src/_modules/embedding/_services/fam-lmstudio-embedding.provider.js +112 -0
- package/build/src/_modules/embedding/_services/fam-mock-embedding.provider.js +64 -0
- package/build/src/_modules/embedding/_services/fam-openai-embedding.provider.js +64 -0
- package/build/src/_modules/embedding/_services/fam-vector-search.control-service.js +244 -0
- package/build/src/_modules/embedding/index.js +40 -0
- package/build/src/_modules/ingest/_collections/fam-content-hash.util.js +35 -0
- package/build/src/_modules/ingest/_collections/fam-file-routing.util.js +95 -0
- package/build/src/_modules/ingest/_collections/fam-glob-match.util.js +84 -0
- package/build/src/_modules/ingest/_collections/fam-md-chunker.util.js +164 -0
- package/build/src/_modules/ingest/_collections/fam-scan-path.util.js +91 -0
- package/build/src/_modules/ingest/_collections/fam-secret-exclude.util.js +54 -0
- package/build/src/_modules/ingest/_collections/fam-sliding-chunker.util.js +76 -0
- package/build/src/_modules/ingest/_collections/fam-ts-chunker.util.js +316 -0
- package/build/src/_modules/ingest/_models/interfaces/fam-ingest.interface.js +2 -0
- package/build/src/_modules/ingest/_services/fam-chunker.control-service.js +114 -0
- package/build/src/_modules/ingest/_services/fam-delta-compare.util.js +74 -0
- package/build/src/_modules/ingest/_services/fam-ingest-run.data-service.js +85 -0
- package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +384 -0
- package/build/src/_modules/ingest/_services/fam-scan.control-service.js +211 -0
- package/build/src/_modules/ingest/index.js +46 -0
- package/build/src/_modules/mcp/_collections/fam-core-tools.const.js +186 -0
- package/build/src/_modules/mcp/_models/interfaces/fam-mcp.interface.js +31 -0
- package/build/src/_modules/mcp/_services/fam-capabilities-tool.service.js +111 -0
- package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +1180 -0
- package/build/src/_modules/mcp/_services/fam-mcp-adapter.service.js +123 -0
- package/build/src/_modules/mcp/_services/fam-mcp-server.service.js +69 -0
- package/build/src/_modules/mcp/_services/fam-read-tool.service.js +99 -0
- package/build/src/_modules/mcp/_services/fam-write-tool.service.js +460 -0
- package/build/src/_modules/mcp/index.js +35 -0
- package/build/src/_modules/migration/_collections/fam-claude-mem-normalize.util.js +166 -0
- package/build/src/_modules/migration/_collections/fam-import-content-hash.util.js +38 -0
- package/build/src/_modules/migration/_collections/fam-target-mapping.util.js +90 -0
- package/build/src/_modules/migration/_enums/fam-claude-mem-source.type-enum.js +20 -0
- package/build/src/_modules/migration/_models/interfaces/fam-claude-mem.interface.js +26 -0
- package/build/src/_modules/migration/_services/fam-claude-mem-export-reader.service.js +134 -0
- package/build/src/_modules/migration/_services/fam-claude-mem-import.control-service.js +533 -0
- package/build/src/_modules/migration/_services/fam-claude-mem-sqlite-reader.service.js +144 -0
- package/build/src/_modules/migration/_services/fam-claude-mem-worker-reader.service.js +115 -0
- package/build/src/_modules/migration/_services/fam-import-dedup.data-service.js +102 -0
- package/build/src/_modules/migration/index.js +38 -0
- package/build/src/_modules/retrieval/_models/interfaces/fam-retrieval.interface.js +2 -0
- package/build/src/_modules/retrieval/_services/fam-retrieval-candidate.data-service.js +67 -0
- package/build/src/_modules/retrieval/_services/fam-retrieval-suggestions.util.js +182 -0
- package/build/src/_modules/retrieval/_services/fam-retrieval.control-service.js +282 -0
- package/build/src/_modules/retrieval/index.js +22 -0
- package/build/src/_modules/scope-reference/_collections/fam-fuzzy-match.util.js +86 -0
- package/build/src/_modules/scope-reference/_collections/fam-scope-normalize.util.js +47 -0
- package/build/src/_modules/scope-reference/_models/interfaces/fam-reference-resolution.interface.js +2 -0
- package/build/src/_modules/scope-reference/_models/interfaces/fam-resolution-trace.interface.js +2 -0
- package/build/src/_modules/scope-reference/_services/fam-reference.data-service.js +179 -0
- package/build/src/_modules/scope-reference/_services/fam-scope-resolver.control-service.js +473 -0
- package/build/src/_modules/scope-reference/_services/fam-scope.data-service.js +215 -0
- package/build/src/_modules/scope-reference/index.js +26 -0
- package/build/src/_routes/server/api/api.controller.js +400 -0
- package/build/src/_routes/server/client-app/client-app.control-service.js +132 -0
- package/build/src/_routes/server/client-app/client-app.controller.js +35 -0
- package/build/src/_routes/server/config/config.control-service.js +476 -0
- package/build/src/_routes/server/config/config.data-service.js +49 -0
- package/build/src/_routes/server/errors/errors.control-service.js +123 -0
- package/build/src/_routes/server/errors/errors.controller.js +65 -0
- package/build/src/_routes/server/errors/errors.data-service.js +80 -0
- package/build/src/_routes/server/server-status/server-status.control-service.js +19 -0
- package/build/src/_routes/server/server-status/server-status.controller.js +39 -0
- package/build/src/app.server.js +122 -0
- package/build/src/environments/environment.js +20 -0
- package/build/src/index.js +18 -0
- package/client-dist/chunk-GHKRM4SM.js +1 -0
- package/client-dist/chunk-LMTL7GA3.js +575 -0
- package/client-dist/index.html +17 -0
- package/client-dist/main-2KWB3QYK.js +2 -0
- package/client-dist/polyfills-HGDOEU5L.js +2 -0
- package/client-dist/styles-3J7JD5YE.css +1 -0
- package/package.json +96 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_ScopeResolver_ControlService = void 0;
|
|
4
|
+
const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
|
|
5
|
+
const error_codes_const_1 = require("../../../_collections/error-codes.const");
|
|
6
|
+
const fam_error_factory_util_1 = require("../../../_collections/fam-error-factory.util");
|
|
7
|
+
const embedding_1 = require("../../embedding");
|
|
8
|
+
const config_control_service_1 = require("../../../_routes/server/config/config.control-service");
|
|
9
|
+
const fam_reference_data_service_1 = require("./fam-reference.data-service");
|
|
10
|
+
const fam_scope_data_service_1 = require("./fam-scope.data-service");
|
|
11
|
+
const fam_scope_normalize_util_1 = require("../_collections/fam-scope-normalize.util");
|
|
12
|
+
/** A `confident` és a `ambiguous-multi` szétválasztó score-gap (a győztes és a 2. közti különbség). */
|
|
13
|
+
const AMBIGUITY_SCORE_GAP = 0.05;
|
|
14
|
+
/**
|
|
15
|
+
* `FAM_ScopeResolver_ControlService` (SP-3.3/SP-3.4, dsgn-002 §3/§4) — a scope/reference feloldás
|
|
16
|
+
* EGYETLEN control-service-e. Singleton. Felelősségek:
|
|
17
|
+
*
|
|
18
|
+
* - **write-resolve** (`resolveWrite`, dsgn-002 §3.2): raw scopePath layerenként → reference-resolve
|
|
19
|
+
* → canonical scopeId-lánc → hiányzó scope auto-regiszt (`createdBy='agent-write'`) → mentésre kész
|
|
20
|
+
* `scopePath`. Bizonytalan layer → `FAM-REF-RESOLVE-*` hiba (SP-3.4), NEM auto-regiszt.
|
|
21
|
+
* - **read-expand** (`expandForRead`, dsgn-002 §3.3): raw scopeFilter → reference-expand → canonical
|
|
22
|
+
* subtree-prefilter scopeId-halmaz (subtree-szemantika). Filter nélkül → `null` (teljes tár, NEM hiba).
|
|
23
|
+
* - **reference-cache** (TTL `reference.cacheTtlMs`, default 60000): a `findActive()` korpusz in-memory
|
|
24
|
+
* snapshot-ja; `rebuildReferenceIndex()` azonnal invalidál + újratölt.
|
|
25
|
+
* - **`scopeExists`** (MP-7 deferred `FAM-CONFIG-SET-006`-hoz): egy `scopeId` aktív-e a fán.
|
|
26
|
+
*
|
|
27
|
+
* **Bizonytalanság (SP-3.4, dsgn-002 §4):** nincs-match + bizonytalan → részletes hiba; van-match +
|
|
28
|
+
* bizonytalan → eredmény + `uncertaintyNotes[]` (NEM hiba). Egyik ág sem néma.
|
|
29
|
+
*
|
|
30
|
+
* **FIGYELEM (memory: dynts_dataservice_eager_resolve):** NEM tartunk élő DataService mezőt; minden
|
|
31
|
+
* DB-művelet előtt lazy `new FAM_Scope_DataService` / `new FAM_Reference_DataService`.
|
|
32
|
+
*/
|
|
33
|
+
class FAM_ScopeResolver_ControlService {
|
|
34
|
+
static _instance;
|
|
35
|
+
/** Default issuer a feloldás-műveletekhez (a hibák / cost-event issuer-éhez). */
|
|
36
|
+
issuer = 'FAM_ScopeResolver_ControlService';
|
|
37
|
+
/** A reference-korpusz in-memory snapshot-ja (`null` = még nincs / invalidált — újratölt). */
|
|
38
|
+
referenceCache = null;
|
|
39
|
+
static getInstance() {
|
|
40
|
+
if (!FAM_ScopeResolver_ControlService._instance) {
|
|
41
|
+
FAM_ScopeResolver_ControlService._instance = new FAM_ScopeResolver_ControlService();
|
|
42
|
+
}
|
|
43
|
+
return FAM_ScopeResolver_ControlService._instance;
|
|
44
|
+
}
|
|
45
|
+
// =========================================================================
|
|
46
|
+
// REFERENCE-CACHE (dsgn-002 §3.1, SP-3.3)
|
|
47
|
+
// =========================================================================
|
|
48
|
+
/**
|
|
49
|
+
* Az aktív reference-korpusz a TTL-cache-ből (dsgn-002 §3.1). Ha a snapshot frissebb a
|
|
50
|
+
* `reference.cacheTtlMs`-nél, a cache-elt listát adja (NINCS DB-kör); egyébként `findActive()` +
|
|
51
|
+
* cache-frissítés. A `rebuildReferenceIndex()` azonnal invalidálja.
|
|
52
|
+
*/
|
|
53
|
+
async getActiveReferences() {
|
|
54
|
+
const ttlMs = await this.resolveCacheTtlMs();
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
if (this.referenceCache && (now - this.referenceCache.loadedAt) < ttlMs) {
|
|
57
|
+
return this.referenceCache.references;
|
|
58
|
+
}
|
|
59
|
+
const reference_DS = new fam_reference_data_service_1.FAM_Reference_DataService({ issuer: this.issuer });
|
|
60
|
+
const references = await reference_DS.findActive();
|
|
61
|
+
this.referenceCache = { references: references, loadedAt: now };
|
|
62
|
+
return references;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* `rebuildReferenceIndex()` (dsgn-002 §3.1, a `rebuild_reference_index` capability — MP-6 hívja).
|
|
66
|
+
* Azonnal invalidál + újratölt: a frissen felvett reference azonnal látszik (a TTL bevárása
|
|
67
|
+
* nélkül). Hiba esetén `FAM-REF-INDEX-001` + a régi cache-szel DEGRADÁLVA tovább-működés (NEM
|
|
68
|
+
* dob el mindent — dsgn-008 §3 index-rebuild ág), `warn` surfacing.
|
|
69
|
+
*/
|
|
70
|
+
async rebuildReferenceIndex() {
|
|
71
|
+
try {
|
|
72
|
+
const reference_DS = new fam_reference_data_service_1.FAM_Reference_DataService({ issuer: this.issuer });
|
|
73
|
+
const references = await reference_DS.findActive();
|
|
74
|
+
this.referenceCache = { references: references, loadedAt: Date.now() };
|
|
75
|
+
return { count: references.length, stale: false };
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
// Degradált tovább-működés: a régi cache marad (ha van), NEM teljes resolver-leállás.
|
|
79
|
+
fsm_dynamo_1.DyFM_Log.warn(`[FAM reference-index] rebuild sikertelen, a régi cache marad: ${error?.message}`);
|
|
80
|
+
throw fam_error_factory_util_1.FAM_Error_Util.create({
|
|
81
|
+
errorCode: error_codes_const_1.FAM_ERROR_CODES.refIndexRebuild,
|
|
82
|
+
message: 'A reference-index újraépítése sikertelen — a feloldó a korábbi (esetleg elavult) '
|
|
83
|
+
+ 'cache-szel működik tovább. A friss reference-bejegyzések a következő sikeres rebuildig nem látszanak.',
|
|
84
|
+
issuer: this.issuer,
|
|
85
|
+
context: { operation: 'rebuild-reference-index' },
|
|
86
|
+
cause: error,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** A reference-cache TTL feloldása a config-ból (`reference.cacheTtlMs`, default 60000). */
|
|
91
|
+
async resolveCacheTtlMs() {
|
|
92
|
+
const resolved = await config_control_service_1.FAM_Config_ControlService.getInstance().resolve('reference.cacheTtlMs');
|
|
93
|
+
return typeof resolved.value === 'number' ? resolved.value : 60000;
|
|
94
|
+
}
|
|
95
|
+
/** A fuzzy-match konfidencia-küszöb a config-ból (`reference.fuzzyMinScore`, default 0.75). */
|
|
96
|
+
async resolveFuzzyMinScore() {
|
|
97
|
+
const resolved = await config_control_service_1.FAM_Config_ControlService.getInstance().resolve('reference.fuzzyMinScore');
|
|
98
|
+
return typeof resolved.value === 'number' ? resolved.value : 0.75;
|
|
99
|
+
}
|
|
100
|
+
/** Az auto-create-scope kapcsoló a config-ból (`reference.autoCreateScope`, default true). */
|
|
101
|
+
async resolveAutoCreateScope() {
|
|
102
|
+
const resolved = await config_control_service_1.FAM_Config_ControlService.getInstance().resolve('reference.autoCreateScope');
|
|
103
|
+
return typeof resolved.value === 'boolean' ? resolved.value : true;
|
|
104
|
+
}
|
|
105
|
+
// =========================================================================
|
|
106
|
+
// FUZZY-RESOLVE (dsgn-002 §3.1, SP-3.2 fúzió) — exact → string-fuzzy → vektor-fuzzy
|
|
107
|
+
// =========================================================================
|
|
108
|
+
/**
|
|
109
|
+
* `fuzzyResolve(rawToken, context)` (SP-3.2) — egyesíti az exact + string-fuzzy + vektor-fuzzy
|
|
110
|
+
* kandidátusokat EGY rendezett, pontszámos listába (dsgn-002 §3.1). Score-fúzió: exact=1.0;
|
|
111
|
+
* a string- és vektor-kandidátusok a saját score-jukkal; per-canonical a LEGJOBB score nyer
|
|
112
|
+
* (dedup canonicalTerm szerint). A `context` (scopeLayer/ragScopes) a nem-releváns kandidátusokat
|
|
113
|
+
* kizárja. A vektor-ág graceful: embedding-hiány → string-only + warn (NEM crash).
|
|
114
|
+
*/
|
|
115
|
+
async fuzzyResolve(rawToken, context) {
|
|
116
|
+
const reference_DS = new fam_reference_data_service_1.FAM_Reference_DataService({ issuer: this.issuer });
|
|
117
|
+
const allReferences = await this.getActiveReferences();
|
|
118
|
+
const references = reference_DS.filterByContext(allReferences, context);
|
|
119
|
+
const candidates = [];
|
|
120
|
+
// (1) Exact fast-path (score 1.0).
|
|
121
|
+
const exact = reference_DS.exactMatch(rawToken, references);
|
|
122
|
+
if (exact) {
|
|
123
|
+
candidates.push({
|
|
124
|
+
canonicalTerm: exact.canonicalTerm ?? '',
|
|
125
|
+
score: 1.0,
|
|
126
|
+
matchSource: 'exact',
|
|
127
|
+
canonicalScopeRef: exact.canonicalScopeRef,
|
|
128
|
+
scopeLayer: exact.scopeLayer,
|
|
129
|
+
referenceId: exact._id,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
// (2) String-fuzzy (Levenshtein) — a katalogizálatlan elírásokra.
|
|
133
|
+
candidates.push(...reference_DS.stringFuzzyMatch(rawToken, references));
|
|
134
|
+
// (3) Vektor-fuzzy (cosine) — soft dep MP-2; graceful degrade embedding-hiányra.
|
|
135
|
+
const queryVector = await this.embedTokenSafe(rawToken);
|
|
136
|
+
if (queryVector.length) {
|
|
137
|
+
candidates.push(...reference_DS.vectorMatch(queryVector, references));
|
|
138
|
+
}
|
|
139
|
+
return this.dedupAndSort(candidates);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* A token embeddelése a vektor-fuzzy-hoz (MP-2 `FAM_Embedding_ControlService`). Bármilyen hiba /
|
|
143
|
+
* üres pool / nincs provider → üres vektor + `warn` (graceful degrade — a string-fuzzy ág teljes
|
|
144
|
+
* értékű marad MP-2 nélkül; dsgn-002 §3.1 SP-3.2 kockázat). NEM dob, NEM néma (warn-log).
|
|
145
|
+
*/
|
|
146
|
+
async embedTokenSafe(rawToken) {
|
|
147
|
+
try {
|
|
148
|
+
const vector = await embedding_1.FAM_Embedding_ControlService.getInstance().embedOne({
|
|
149
|
+
text: rawToken,
|
|
150
|
+
callType: 'embed-query',
|
|
151
|
+
issuer: this.issuer,
|
|
152
|
+
});
|
|
153
|
+
return vector;
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
fsm_dynamo_1.DyFM_Log.warn(`[FAM reference vektor-fuzzy] embedding nem elérhető a('${rawToken}') tokenre → string-only fallback: `
|
|
157
|
+
+ `${error?.message}`);
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/** Kandidátus-dedup canonicalTerm szerint (a legjobb score nyer) + csökkenő rendezés. */
|
|
162
|
+
dedupAndSort(candidates) {
|
|
163
|
+
const bestByCanonical = new Map();
|
|
164
|
+
for (const candidate of candidates) {
|
|
165
|
+
const key = fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(candidate.canonicalTerm);
|
|
166
|
+
const existing = bestByCanonical.get(key);
|
|
167
|
+
if (!existing || candidate.score > existing.score) {
|
|
168
|
+
bestByCanonical.set(key, candidate);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return Array.from(bestByCanonical.values()).sort((a, b) => b.score - a.score);
|
|
172
|
+
}
|
|
173
|
+
// =========================================================================
|
|
174
|
+
// BIZONYTALANSÁG-ELDÖNTÉS (dsgn-002 §4, SP-3.4)
|
|
175
|
+
// =========================================================================
|
|
176
|
+
/**
|
|
177
|
+
* `classifyResolution(candidates, minScore)` (SP-3.4) — küszöb-alapú feloldás-osztály:
|
|
178
|
+
* - `no-match`: a legjobb score < `minScore` (vagy nincs kandidátus),
|
|
179
|
+
* - `ambiguous-multi`: a top-2 mindkettő >= `minScore` ÉS a score-gap < `AMBIGUITY_SCORE_GAP`,
|
|
180
|
+
* - `confident`: 1 magas-konfidenciájú, jól-elkülönülő kandidátus.
|
|
181
|
+
*/
|
|
182
|
+
classifyResolution(candidates, minScore) {
|
|
183
|
+
if (!candidates.length || candidates[0].score < minScore) {
|
|
184
|
+
return 'no-match';
|
|
185
|
+
}
|
|
186
|
+
if (candidates.length >= 2 && candidates[1].score >= minScore
|
|
187
|
+
&& (candidates[0].score - candidates[1].score) < AMBIGUITY_SCORE_GAP) {
|
|
188
|
+
return 'ambiguous-multi';
|
|
189
|
+
}
|
|
190
|
+
return 'confident';
|
|
191
|
+
}
|
|
192
|
+
// =========================================================================
|
|
193
|
+
// WRITE-RESOLVE (dsgn-002 §3.2, SP-3.3) — scope KÖTELEZŐ
|
|
194
|
+
// =========================================================================
|
|
195
|
+
/**
|
|
196
|
+
* `resolveForWrite(rawScopePath)` (dsgn-002 §3.2). A scope a write-on KÖTELEZŐ: üres/hiányzó
|
|
197
|
+
* rawScopePath → `FAM-SCOPE-WRITE-001` (NEM csendes default). Layerenként:
|
|
198
|
+
* (1) `fuzzyResolve` (scopeLayer-szűkítve) → `classifyResolution`;
|
|
199
|
+
* (2) `confident` + scope-match → annak a `scopeId`-ja (a parent az előző layer feloldott scopeId-ja);
|
|
200
|
+
* (3) `confident` reference-match scope-ref NÉLKÜL VAGY `no-match` → ha `autoCreateScope` → a
|
|
201
|
+
* canonical névvel auto-regiszt (`createdBy='agent-write'`); ha `ambiguous-multi` → SP-3.4
|
|
202
|
+
* `FAM-REF-RESOLVE-002` hiba.
|
|
203
|
+
* Visszatérés: canonical `scopePath` (root→leaf) + layer-meta + `uncertaintyNotes[]` (kötelező mező).
|
|
204
|
+
*/
|
|
205
|
+
async resolveForWrite(rawScopePath) {
|
|
206
|
+
if (!rawScopePath || !rawScopePath.length) {
|
|
207
|
+
throw fam_error_factory_util_1.FAM_Error_Util.create({
|
|
208
|
+
errorCode: error_codes_const_1.FAM_ERROR_CODES.scopeWriteRequired,
|
|
209
|
+
message: 'A scope KÖTELEZŐ minden tartalom-műveletnél (create/scan/import) — üres scopePath nem fogadható el. '
|
|
210
|
+
+ 'Adj meg legalább egy layert (pl. organization/project).',
|
|
211
|
+
issuer: this.issuer,
|
|
212
|
+
context: { operation: 'resolve-for-write' },
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
const minScore = await this.resolveFuzzyMinScore();
|
|
216
|
+
const autoCreate = await this.resolveAutoCreateScope();
|
|
217
|
+
const scope_DS = new fam_scope_data_service_1.FAM_Scope_DataService({ issuer: this.issuer });
|
|
218
|
+
const layers = [];
|
|
219
|
+
const uncertaintyNotes = [];
|
|
220
|
+
let parentScopeId = undefined;
|
|
221
|
+
for (const rawLayer of rawScopePath) {
|
|
222
|
+
const candidates = await this.fuzzyResolve(rawLayer.rawName, { scopeLayer: rawLayer.layer });
|
|
223
|
+
const classification = this.classifyResolution(candidates, minScore);
|
|
224
|
+
if (classification === 'ambiguous-multi') {
|
|
225
|
+
throw this.ambiguousError(rawLayer, candidates, minScore);
|
|
226
|
+
}
|
|
227
|
+
const resolved = await this.resolveOneWriteLayer({
|
|
228
|
+
rawLayer: rawLayer,
|
|
229
|
+
candidates: candidates,
|
|
230
|
+
classification: classification,
|
|
231
|
+
parentScopeId: parentScopeId,
|
|
232
|
+
autoCreate: autoCreate,
|
|
233
|
+
scopeDataService: scope_DS,
|
|
234
|
+
});
|
|
235
|
+
layers.push(resolved);
|
|
236
|
+
parentScopeId = resolved.scopeRef.scopeId;
|
|
237
|
+
// Van-match-bizonytalan: confident, de fuzzy (NEM exact) volt → uncertaintyNote (NEM hiba).
|
|
238
|
+
if (classification === 'confident' && candidates.length && candidates[0].matchSource !== 'exact') {
|
|
239
|
+
uncertaintyNotes.push(this.toUncertaintyNote(rawLayer.rawName, candidates));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
scopePath: layers.map((layer) => layer.scopeRef),
|
|
244
|
+
layers: layers,
|
|
245
|
+
uncertaintyNotes: uncertaintyNotes,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Egy write-layer feloldása (a `resolveForWrite` belső lépése). `confident` + scope-match → a
|
|
250
|
+
* meglévő scope; egyébként (confident-de-csak-fogalmi-alias VAGY no-match) → auto-regiszt
|
|
251
|
+
* (ha `autoCreate`), KÜLÖNBEN `FAM-REF-RESOLVE-001` (nincs match + nincs auto-create).
|
|
252
|
+
*/
|
|
253
|
+
async resolveOneWriteLayer(set) {
|
|
254
|
+
// (a) confident reference-match, ami EGY konkrét scope-entitásra mutat → annak a scopeId-ja.
|
|
255
|
+
if (set.classification === 'confident' && set.candidates.length && set.candidates[0].canonicalScopeRef?.scopeId) {
|
|
256
|
+
const ref = set.candidates[0].canonicalScopeRef;
|
|
257
|
+
return { scopeRef: ref, created: false };
|
|
258
|
+
}
|
|
259
|
+
// (b) confident, de csak fogalmi alias (nincs scope-ref): a canonical néven find-or-create.
|
|
260
|
+
// (c) no-match: a nyers néven find-or-create — DE csak ha autoCreate; egyébként hiba.
|
|
261
|
+
const isConfident = set.classification === 'confident' && set.candidates.length > 0;
|
|
262
|
+
const canonicalName = isConfident ? set.candidates[0].canonicalTerm : set.rawLayer.rawName;
|
|
263
|
+
// Determinisztikus de-dup: a (normalizált név + layer + parent) hármasra már létezik-e?
|
|
264
|
+
const existing = await set.scopeDataService.findByCanonical(set.rawLayer.layer, canonicalName, set.parentScopeId);
|
|
265
|
+
if (existing.length && existing[0]._id) {
|
|
266
|
+
return { scopeRef: this.toScopeRef(existing[0]), created: false };
|
|
267
|
+
}
|
|
268
|
+
if (!set.autoCreate) {
|
|
269
|
+
throw this.noMatchError(set.rawLayer, set.candidates, await this.resolveFuzzyMinScore());
|
|
270
|
+
}
|
|
271
|
+
// Auto-regiszt (createdBy='agent-write', parent = az előző layer scopeId-ja).
|
|
272
|
+
const created = await set.scopeDataService.findOrCreateScope({
|
|
273
|
+
layer: set.rawLayer.layer,
|
|
274
|
+
canonicalName: canonicalName,
|
|
275
|
+
parentScopeId: set.parentScopeId,
|
|
276
|
+
createdBy: 'agent-write',
|
|
277
|
+
});
|
|
278
|
+
return { scopeRef: this.toScopeRef(created.scope), created: created.created };
|
|
279
|
+
}
|
|
280
|
+
// =========================================================================
|
|
281
|
+
// READ-EXPAND (dsgn-002 §3.3, SP-3.3) — scope OPCIONÁLIS prefilter
|
|
282
|
+
// =========================================================================
|
|
283
|
+
/**
|
|
284
|
+
* `expandForRead(rawScopeFilter?)` (dsgn-002 §3.3). Filter NÉLKÜL → `scopeIdSet: null` (teljes
|
|
285
|
+
* táron keresés — NEM hiba). Filterrel: layerenként feloldás → a feloldott scope-ok
|
|
286
|
+
* `getSubtreeScopeIds` UNIÓJA = canonical prefilter scopeId-halmaz (subtree-szemantika: a megadott
|
|
287
|
+
* layer + alatta minden leszármazott). Részleges filter (csak felső layer) → tág subtree (DESIGN).
|
|
288
|
+
* Van-match-bizonytalan → `uncertaintyNotes[]` (NEM hiba). Nincs-match egy layeren → `FAM-REF-RESOLVE-001`.
|
|
289
|
+
*/
|
|
290
|
+
async expandForRead(rawScopeFilter) {
|
|
291
|
+
if (!rawScopeFilter || !rawScopeFilter.length) {
|
|
292
|
+
return { scopeIdSet: null, uncertaintyNotes: [] };
|
|
293
|
+
}
|
|
294
|
+
const minScore = await this.resolveFuzzyMinScore();
|
|
295
|
+
const scope_DS = new fam_scope_data_service_1.FAM_Scope_DataService({ issuer: this.issuer });
|
|
296
|
+
const scopeIdSet = new Set();
|
|
297
|
+
const uncertaintyNotes = [];
|
|
298
|
+
for (const rawLayer of rawScopeFilter) {
|
|
299
|
+
const candidates = await this.fuzzyResolve(rawLayer.rawName, { scopeLayer: rawLayer.layer });
|
|
300
|
+
const classification = this.classifyResolution(candidates, minScore);
|
|
301
|
+
const scopeIds = await this.resolveReadLayerScopeIds({
|
|
302
|
+
rawLayer: rawLayer,
|
|
303
|
+
candidates: candidates,
|
|
304
|
+
classification: classification,
|
|
305
|
+
minScore: minScore,
|
|
306
|
+
scopeDataService: scope_DS,
|
|
307
|
+
});
|
|
308
|
+
for (const scopeId of scopeIds) {
|
|
309
|
+
scopeIdSet.add(scopeId);
|
|
310
|
+
}
|
|
311
|
+
if (classification === 'confident' && candidates.length && candidates[0].matchSource !== 'exact') {
|
|
312
|
+
uncertaintyNotes.push(this.toUncertaintyNote(rawLayer.rawName, candidates));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return { scopeIdSet: Array.from(scopeIdSet), uncertaintyNotes: uncertaintyNotes };
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Egy read-filter-layer → subtree scopeId-halmaz. A feloldott scope (reference scope-ref VAGY
|
|
319
|
+
* canonical-név-match a fán) subtree-ját adja (`getSubtreeScopeIds`). `ambiguous-multi` → minden
|
|
320
|
+
* közeli kandidátus subtree-ja bekerül (a read tág, NEM dob — a bizonytalanság uncertaintyNote).
|
|
321
|
+
* `no-match` → `FAM-REF-RESOLVE-001` (a read-filter feloldhatatlan, nincs csendes üres-prefilter).
|
|
322
|
+
*/
|
|
323
|
+
async resolveReadLayerScopeIds(set) {
|
|
324
|
+
if (set.classification === 'no-match') {
|
|
325
|
+
throw this.noMatchError(set.rawLayer, set.candidates, set.minScore);
|
|
326
|
+
}
|
|
327
|
+
// A confident (top-1) + az ambiguous-multi (a >= minScore közeli kandidátusok) scope-jai.
|
|
328
|
+
const accepted = set.classification === 'ambiguous-multi'
|
|
329
|
+
? set.candidates.filter((candidate) => candidate.score >= set.minScore)
|
|
330
|
+
: [set.candidates[0]];
|
|
331
|
+
const result = [];
|
|
332
|
+
for (const candidate of accepted) {
|
|
333
|
+
const rootScopeId = await this.candidateToScopeId({
|
|
334
|
+
rawLayer: set.rawLayer,
|
|
335
|
+
candidate: candidate,
|
|
336
|
+
scopeDataService: set.scopeDataService,
|
|
337
|
+
});
|
|
338
|
+
if (!rootScopeId) {
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
const subtree = await set.scopeDataService.getSubtreeScopeIds(rootScopeId);
|
|
342
|
+
result.push(...subtree);
|
|
343
|
+
}
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Egy kandidátus → a fán létező scopeId (read-expand). Ha a kandidátus konkrét scope-ref-et hordoz
|
|
348
|
+
* → annak az scopeId-ja; egyébként a canonical néven keresünk a layerben (a read NEM hoz létre
|
|
349
|
+
* scope-ot — csak prefilter). Nincs a fán → undefined (nincs subtree).
|
|
350
|
+
*/
|
|
351
|
+
async candidateToScopeId(set) {
|
|
352
|
+
if (set.candidate.canonicalScopeRef?.scopeId) {
|
|
353
|
+
return set.candidate.canonicalScopeRef.scopeId;
|
|
354
|
+
}
|
|
355
|
+
const matches = await set.scopeDataService.findByCanonical(set.rawLayer.layer, set.candidate.canonicalTerm);
|
|
356
|
+
return matches.length ? matches[0]._id : undefined;
|
|
357
|
+
}
|
|
358
|
+
// =========================================================================
|
|
359
|
+
// scopeExists (MP-7 deferred FAM-CONFIG-SET-006, SP-3.3)
|
|
360
|
+
// =========================================================================
|
|
361
|
+
/**
|
|
362
|
+
* `scopeExists(scopeId)` (SP-3.3) — egy `scopeId` AKTÍV-e a scope-fán. Az MP-7 config-set
|
|
363
|
+
* scope-szintű validációja (`FAM-CONFIG-SET-006`) ezt a lookup-ot halasztotta MP-3-ra: a
|
|
364
|
+
* `set('x', v, { level: scope, scopeId })` ezzel ellenőrizheti a tényleges scope-létet.
|
|
365
|
+
*/
|
|
366
|
+
async scopeExists(scopeId) {
|
|
367
|
+
if (!scopeId) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
const scope_DS = new fam_scope_data_service_1.FAM_Scope_DataService({ issuer: this.issuer });
|
|
371
|
+
const scope = await scope_DS.findScopeById(scopeId);
|
|
372
|
+
return Boolean(scope && scope._id);
|
|
373
|
+
}
|
|
374
|
+
// =========================================================================
|
|
375
|
+
// TRACE (dsgn-002 §4, dsgn-003 §4.2) — a debug-capability (MP-6) fogyasztja
|
|
376
|
+
// =========================================================================
|
|
377
|
+
/**
|
|
378
|
+
* `traceResolution(rawTokens, mode)` (SP-3.3) — a feloldás lépés-naplója (token → normalizált →
|
|
379
|
+
* kandidátusok+score → döntés). A `trace_scope_resolution` / `trace_reference_expansion`
|
|
380
|
+
* debug-capability (MP-6) ezt adja ki. Nem mutál (nem hoz létre scope-ot) — csak feloldás-trace.
|
|
381
|
+
*/
|
|
382
|
+
async traceResolution(rawTokens, mode) {
|
|
383
|
+
const minScore = await this.resolveFuzzyMinScore();
|
|
384
|
+
const queryVectorAvailable = (await this.embedTokenSafe(rawTokens[0]?.rawName ?? '')).length > 0;
|
|
385
|
+
const steps = [];
|
|
386
|
+
for (const rawLayer of rawTokens) {
|
|
387
|
+
const context = mode === 'query-expand' ? undefined : { scopeLayer: rawLayer.layer };
|
|
388
|
+
const candidates = await this.fuzzyResolve(rawLayer.rawName, context);
|
|
389
|
+
const classification = this.classifyResolution(candidates, minScore);
|
|
390
|
+
const decided = classification === 'confident' ? candidates[0] : undefined;
|
|
391
|
+
steps.push({
|
|
392
|
+
rawToken: rawLayer.rawName,
|
|
393
|
+
normalizedForm: fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(rawLayer.rawName),
|
|
394
|
+
layer: mode === 'query-expand' ? undefined : rawLayer.layer,
|
|
395
|
+
candidates: candidates,
|
|
396
|
+
classification: classification,
|
|
397
|
+
decidedMatchSource: decided?.matchSource,
|
|
398
|
+
decidedCanonical: decided?.canonicalTerm,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
const cacheAge = this.referenceCache ? Date.now() - this.referenceCache.loadedAt : 0;
|
|
402
|
+
return {
|
|
403
|
+
mode: mode,
|
|
404
|
+
steps: steps,
|
|
405
|
+
vectorAvailable: queryVectorAvailable,
|
|
406
|
+
referenceCacheAgeMs: cacheAge,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
// =========================================================================
|
|
410
|
+
// UNCERTAINTY + hiba-építés (dsgn-002 §4, SP-3.4)
|
|
411
|
+
// =========================================================================
|
|
412
|
+
/** Egy győztes kandidátus + alternatívák → `uncertaintyNote` (van-match-bizonytalan, NEM hiba). */
|
|
413
|
+
toUncertaintyNote(rawToken, candidates) {
|
|
414
|
+
const winner = candidates[0];
|
|
415
|
+
return {
|
|
416
|
+
rawToken: rawToken,
|
|
417
|
+
normalizedForm: fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(rawToken),
|
|
418
|
+
resolvedTo: winner.canonicalTerm,
|
|
419
|
+
confidence: winner.score,
|
|
420
|
+
matchSource: winner.matchSource,
|
|
421
|
+
alternatives: candidates.slice(1, 4).map((candidate) => ({ term: candidate.canonicalTerm, score: candidate.score })),
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Nincs-match + bizonytalan → `FAM-REF-RESOLVE-001` (dsgn-002 §4, level=user). DESKRIPTÍV
|
|
426
|
+
* payload: feloldatlan token, normalizált forma, legközelebbi kandidátusok (term+score),
|
|
427
|
+
* vizsgált layer, konkrét javaslat. Üres `[]` SOSEM helyettesíti a hibát (no-silent-failure).
|
|
428
|
+
*/
|
|
429
|
+
noMatchError(rawLayer, candidates, minScore) {
|
|
430
|
+
const normalized = fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(rawLayer.rawName);
|
|
431
|
+
const nearest = candidates.length
|
|
432
|
+
? candidates.slice(0, 3).map((candidate) => `${candidate.canonicalTerm} (${candidate.score.toFixed(2)})`).join(', ')
|
|
433
|
+
: 'nincs egyetlen kandidátus sem';
|
|
434
|
+
return fam_error_factory_util_1.FAM_Error_Util.create({
|
|
435
|
+
errorCode: error_codes_const_1.FAM_ERROR_CODES.refResolveNoMatch,
|
|
436
|
+
message: `A(z) '${rawLayer.layer}' layer '${rawLayer.rawName}' tokenje nem oldható fel `
|
|
437
|
+
+ `(normalizált: '${normalized}'). Legközelebbi kandidátusok: ${nearest} — egyik sem éri el a `
|
|
438
|
+
+ `${minScore} konfidencia-küszöböt. Javaslat: hozz létre reference-bejegyzést erre a névre, `
|
|
439
|
+
+ 'vagy adj meg egy explicit, létező scope nevet.',
|
|
440
|
+
issuer: this.issuer,
|
|
441
|
+
context: { operation: 'resolve-no-match', scopePath: [rawLayer.layer], query: rawLayer.rawName },
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Több magas-konfidenciájú kandidátus (nem egyértelmű) → `FAM-REF-RESOLVE-002` (dsgn-002 §4).
|
|
446
|
+
* DESKRIPTÍV: a token, a normalizált forma, a versengő kandidátusok + score, javaslat.
|
|
447
|
+
*/
|
|
448
|
+
ambiguousError(rawLayer, candidates, minScore) {
|
|
449
|
+
const normalized = fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(rawLayer.rawName);
|
|
450
|
+
const competing = candidates
|
|
451
|
+
.filter((candidate) => candidate.score >= minScore)
|
|
452
|
+
.slice(0, 4)
|
|
453
|
+
.map((candidate) => `${candidate.canonicalTerm} (${candidate.score.toFixed(2)})`)
|
|
454
|
+
.join(', ');
|
|
455
|
+
return fam_error_factory_util_1.FAM_Error_Util.create({
|
|
456
|
+
errorCode: error_codes_const_1.FAM_ERROR_CODES.refResolveAmbiguous,
|
|
457
|
+
message: `A(z) '${rawLayer.layer}' layer '${rawLayer.rawName}' tokenje (normalizált: '${normalized}') `
|
|
458
|
+
+ `több, közel azonos konfidenciájú scope-ra is illeszkedik: ${competing}. A feloldás nem egyértelmű. `
|
|
459
|
+
+ 'Javaslat: adj meg egy pontosabb / explicit nevet, vagy egészítsd ki a scope-láncot a fölérendelt layerrel.',
|
|
460
|
+
issuer: this.issuer,
|
|
461
|
+
context: { operation: 'resolve-ambiguous', scopePath: [rawLayer.layer], query: rawLayer.rawName },
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
/** Egy `FAM_Scope_DataModel` → `FAM_ScopeRef` map. */
|
|
465
|
+
toScopeRef(scope) {
|
|
466
|
+
return {
|
|
467
|
+
scopeId: scope._id ?? '',
|
|
468
|
+
layer: scope.layer ?? '',
|
|
469
|
+
canonicalName: scope.canonicalName ?? '',
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
exports.FAM_ScopeResolver_ControlService = FAM_ScopeResolver_ControlService;
|