@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.
Files changed (117) hide show
  1. package/README.md +345 -0
  2. package/build/package.json +96 -0
  3. package/build/src/_assets/mcp-client-config/README.md +29 -0
  4. package/build/src/_assets/mcp-client-config/claude_desktop_config.json +15 -0
  5. package/build/src/_assets/mcp-client-config/mcp.json +15 -0
  6. package/build/src/_collections/config-catalog.const.js +180 -0
  7. package/build/src/_collections/config-error-codes.const.js +30 -0
  8. package/build/src/_collections/config-presets.const.js +25 -0
  9. package/build/src/_collections/error-banners.const.js +100 -0
  10. package/build/src/_collections/error-codes.const.js +150 -0
  11. package/build/src/_collections/fam-db-models.const.js +37 -0
  12. package/build/src/_collections/fam-entry-bootstrap.util.js +80 -0
  13. package/build/src/_collections/fam-error-context.util.js +90 -0
  14. package/build/src/_collections/fam-error-factory.util.js +64 -0
  15. package/build/src/_enums/fam-config-level.type-enum.js +15 -0
  16. package/build/src/_enums/fam-table.type-enum.js +20 -0
  17. package/build/src/_integration-tests/_helpers/fam-integration-test-setup.util.js +105 -0
  18. package/build/src/_models/data-models/fam-codebase.data-model.js +51 -0
  19. package/build/src/_models/data-models/fam-coding-patterns.data-model.js +58 -0
  20. package/build/src/_models/data-models/fam-config.data-model.js +68 -0
  21. package/build/src/_models/data-models/fam-documents.data-model.js +53 -0
  22. package/build/src/_models/data-models/fam-entry-base-properties.const.js +43 -0
  23. package/build/src/_models/data-models/fam-entry.data-model.js +81 -0
  24. package/build/src/_models/data-models/fam-error.data-model.js +88 -0
  25. package/build/src/_models/data-models/fam-ingest-run.data-model.js +74 -0
  26. package/build/src/_models/data-models/fam-knowledge.data-model.js +48 -0
  27. package/build/src/_models/data-models/fam-memory.data-model.js +55 -0
  28. package/build/src/_models/data-models/fam-reference.data-model.js +67 -0
  29. package/build/src/_models/data-models/fam-rules.data-model.js +51 -0
  30. package/build/src/_models/data-models/fam-scope.data-model.js +52 -0
  31. package/build/src/_models/interfaces/fam-common.interface.js +23 -0
  32. package/build/src/_models/interfaces/fam-config.interface.js +2 -0
  33. package/build/src/_models/interfaces/fam-error.interface.js +2 -0
  34. package/build/src/_modules/embedding/_collections/fam-embedding-pricing.const.js +22 -0
  35. package/build/src/_modules/embedding/_collections/fam-store-registry.const.js +63 -0
  36. package/build/src/_modules/embedding/_models/interfaces/fam-embedding-cost.interface.js +10 -0
  37. package/build/src/_modules/embedding/_models/interfaces/fam-embedding-provider.interface.js +2 -0
  38. package/build/src/_modules/embedding/_models/interfaces/fam-resolved-provider.interface.js +2 -0
  39. package/build/src/_modules/embedding/_services/fam-embedding-bootstrap.control-service.js +52 -0
  40. package/build/src/_modules/embedding/_services/fam-embedding-cost.control-service.js +175 -0
  41. package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +202 -0
  42. package/build/src/_modules/embedding/_services/fam-embedding-preset.control-service.js +66 -0
  43. package/build/src/_modules/embedding/_services/fam-embedding.control-service.js +253 -0
  44. package/build/src/_modules/embedding/_services/fam-entry.data-service.js +64 -0
  45. package/build/src/_modules/embedding/_services/fam-lmstudio-embedding.provider.js +112 -0
  46. package/build/src/_modules/embedding/_services/fam-mock-embedding.provider.js +64 -0
  47. package/build/src/_modules/embedding/_services/fam-openai-embedding.provider.js +64 -0
  48. package/build/src/_modules/embedding/_services/fam-vector-search.control-service.js +244 -0
  49. package/build/src/_modules/embedding/index.js +40 -0
  50. package/build/src/_modules/ingest/_collections/fam-content-hash.util.js +35 -0
  51. package/build/src/_modules/ingest/_collections/fam-file-routing.util.js +95 -0
  52. package/build/src/_modules/ingest/_collections/fam-glob-match.util.js +84 -0
  53. package/build/src/_modules/ingest/_collections/fam-md-chunker.util.js +164 -0
  54. package/build/src/_modules/ingest/_collections/fam-scan-path.util.js +91 -0
  55. package/build/src/_modules/ingest/_collections/fam-secret-exclude.util.js +54 -0
  56. package/build/src/_modules/ingest/_collections/fam-sliding-chunker.util.js +76 -0
  57. package/build/src/_modules/ingest/_collections/fam-ts-chunker.util.js +316 -0
  58. package/build/src/_modules/ingest/_models/interfaces/fam-ingest.interface.js +2 -0
  59. package/build/src/_modules/ingest/_services/fam-chunker.control-service.js +114 -0
  60. package/build/src/_modules/ingest/_services/fam-delta-compare.util.js +74 -0
  61. package/build/src/_modules/ingest/_services/fam-ingest-run.data-service.js +85 -0
  62. package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +384 -0
  63. package/build/src/_modules/ingest/_services/fam-scan.control-service.js +211 -0
  64. package/build/src/_modules/ingest/index.js +46 -0
  65. package/build/src/_modules/mcp/_collections/fam-core-tools.const.js +186 -0
  66. package/build/src/_modules/mcp/_models/interfaces/fam-mcp.interface.js +31 -0
  67. package/build/src/_modules/mcp/_services/fam-capabilities-tool.service.js +111 -0
  68. package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +1180 -0
  69. package/build/src/_modules/mcp/_services/fam-mcp-adapter.service.js +123 -0
  70. package/build/src/_modules/mcp/_services/fam-mcp-server.service.js +69 -0
  71. package/build/src/_modules/mcp/_services/fam-read-tool.service.js +99 -0
  72. package/build/src/_modules/mcp/_services/fam-write-tool.service.js +460 -0
  73. package/build/src/_modules/mcp/index.js +35 -0
  74. package/build/src/_modules/migration/_collections/fam-claude-mem-normalize.util.js +166 -0
  75. package/build/src/_modules/migration/_collections/fam-import-content-hash.util.js +38 -0
  76. package/build/src/_modules/migration/_collections/fam-target-mapping.util.js +90 -0
  77. package/build/src/_modules/migration/_enums/fam-claude-mem-source.type-enum.js +20 -0
  78. package/build/src/_modules/migration/_models/interfaces/fam-claude-mem.interface.js +26 -0
  79. package/build/src/_modules/migration/_services/fam-claude-mem-export-reader.service.js +134 -0
  80. package/build/src/_modules/migration/_services/fam-claude-mem-import.control-service.js +533 -0
  81. package/build/src/_modules/migration/_services/fam-claude-mem-sqlite-reader.service.js +144 -0
  82. package/build/src/_modules/migration/_services/fam-claude-mem-worker-reader.service.js +115 -0
  83. package/build/src/_modules/migration/_services/fam-import-dedup.data-service.js +102 -0
  84. package/build/src/_modules/migration/index.js +38 -0
  85. package/build/src/_modules/retrieval/_models/interfaces/fam-retrieval.interface.js +2 -0
  86. package/build/src/_modules/retrieval/_services/fam-retrieval-candidate.data-service.js +67 -0
  87. package/build/src/_modules/retrieval/_services/fam-retrieval-suggestions.util.js +182 -0
  88. package/build/src/_modules/retrieval/_services/fam-retrieval.control-service.js +282 -0
  89. package/build/src/_modules/retrieval/index.js +22 -0
  90. package/build/src/_modules/scope-reference/_collections/fam-fuzzy-match.util.js +86 -0
  91. package/build/src/_modules/scope-reference/_collections/fam-scope-normalize.util.js +47 -0
  92. package/build/src/_modules/scope-reference/_models/interfaces/fam-reference-resolution.interface.js +2 -0
  93. package/build/src/_modules/scope-reference/_models/interfaces/fam-resolution-trace.interface.js +2 -0
  94. package/build/src/_modules/scope-reference/_services/fam-reference.data-service.js +179 -0
  95. package/build/src/_modules/scope-reference/_services/fam-scope-resolver.control-service.js +473 -0
  96. package/build/src/_modules/scope-reference/_services/fam-scope.data-service.js +215 -0
  97. package/build/src/_modules/scope-reference/index.js +26 -0
  98. package/build/src/_routes/server/api/api.controller.js +400 -0
  99. package/build/src/_routes/server/client-app/client-app.control-service.js +132 -0
  100. package/build/src/_routes/server/client-app/client-app.controller.js +35 -0
  101. package/build/src/_routes/server/config/config.control-service.js +476 -0
  102. package/build/src/_routes/server/config/config.data-service.js +49 -0
  103. package/build/src/_routes/server/errors/errors.control-service.js +123 -0
  104. package/build/src/_routes/server/errors/errors.controller.js +65 -0
  105. package/build/src/_routes/server/errors/errors.data-service.js +80 -0
  106. package/build/src/_routes/server/server-status/server-status.control-service.js +19 -0
  107. package/build/src/_routes/server/server-status/server-status.controller.js +39 -0
  108. package/build/src/app.server.js +122 -0
  109. package/build/src/environments/environment.js +20 -0
  110. package/build/src/index.js +18 -0
  111. package/client-dist/chunk-GHKRM4SM.js +1 -0
  112. package/client-dist/chunk-LMTL7GA3.js +575 -0
  113. package/client-dist/index.html +17 -0
  114. package/client-dist/main-2KWB3QYK.js +2 -0
  115. package/client-dist/polyfills-HGDOEU5L.js +2 -0
  116. package/client-dist/styles-3J7JD5YE.css +1 -0
  117. 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;