@futdevpro/fdp-agent-memory 1.1.137 → 1.1.153

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@futdevpro/fdp-agent-memory",
3
- "version": "1.1.137",
3
+ "version": "1.1.153",
4
4
  "description": "Local-first, vector-backed multi-table agent memory exposed as an MCP server (read/write/capabilities). Public, FDP-Templates-free, no auth.",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -138,19 +138,49 @@ exports.CONFIG_CATALOG = {
138
138
  description: 'Minimum similarity-score küszöb (CCAP default 0 = nincs vágás).',
139
139
  },
140
140
  'read.hydrationWaitMs': {
141
- type: 'number', default: 180000, min: 0, max: 600000, integer: true, levels: ALL_LEVELS,
141
+ type: 'number', default: 20000, min: 0, max: 600000, integer: true, levels: ALL_LEVELS,
142
142
  description: 'A readiness-coordinator hidratálás-bevárás felső korlátja ms-ban: a `read`/`search` MEGVÁRJA a '
143
143
  + 'tár pool-jának (kezdő VAGY újra-) hidratálását ahelyett, hogy `FAM-VEC-SEARCH-001 pool ÜRES`-t dobna. '
144
- + '0 = nincs várakozás (azonnali hiba, ha még nem kész). Boot-kor a ~290k-vektor hidratálás percekig tarthat → '
145
- + 'a default 180s ezt fedi; time-out után tiszta „még hidratál, próbáld újra" hiba (a következő hívás már kész).',
144
+ + '0 = nincs várakozás (azonnali hiba, ha még nem kész). '
145
+ + 'MÉRT INDOK a 20s-re (2026-08-22): a korábbi default 180s volt, ám EGYETLEN MCP-kliensnek sincs ilyen '
146
+ + 'hosszú tool-timeoutja (a Codex 60s-mal futott) — vagyis a szerver egy nem létező hívóra várt, és a '
147
+ + 'jól diagnosztizálható „a pool hidratál, 3/6 tár kész" állapotot ÁTLÁTHATATLAN kliens-oldali timeoutra '
148
+ + 'fordította. Egy agent három azonos újrapróbálkozással 3 percet égetett el úgy, hogy az okot SOHA nem '
149
+ + 'láthatta. 20s továbbra is elnyeli a rövid újra-hidratálási zökkenőt, de a TELJES hideg bootot (~120s) '
150
+ + 'már nem rejti el: azt a hívónak TUDNIA kell, hogy dönthessen (vár, vagy közben mást csinál). '
151
+ + 'A time-out hibája megmondja, mely tárak vannak még hátra. Aki a régi viselkedést akarja, állítsa vissza.',
152
+ },
153
+ 'read.maxChunksPerFile': {
154
+ type: 'number', default: 2, min: 0, max: 50, integer: true, levels: ALL_LEVELS,
155
+ description: 'Fajlonkent legfeljebb ennyi chunk kerul a top-K elejere; a tobbi a lista vegere csuszik '
156
+ + '(0 = kikapcsolva, talalat SOHA nem vesz el). Mert indok (2026-08-22): a top-K slotok 44%-a ugyanannak '
157
+ + 'a fajlnak egy masik darabja volt, egy query-n a TELJES top-5 egyetlen fajlbol allt.',
158
+ },
159
+ 'read.tableMinShare': {
160
+ type: 'number', default: 1, min: 0, max: 20, integer: true, levels: ALL_LEVELS,
161
+ description: 'Tobb-taras query-ben minden talalatot ado tar garantalt helye a top-K-ban (0 = kikapcsolva). '
162
+ + 'Mert indok (2026-08-22): kontrollalt teszten a rules tar 0/3 aranyban tunt el a documents mogott, '
163
+ + 'holott a csak-rules query azonnal hozta a kanonikus szabalyt.',
146
164
  },
147
165
  'read.relevanceFloor': {
148
- type: 'number', default: 0.5, min: 0.0, max: 1.0, levels: ALL_LEVELS,
149
- description: 'Confidence-padló a nyers cosine score-on: a relevancia-JELZÉSEK (totalRelevant / '
150
- + 'denseResults / truncated / suggestions) CSAK az e fölötti hit-eket számolják „magabiztosan '
151
- + 'relevánsnak". A returnedHits ettől függetlenül best-effort (recall). Ha a legjobb score is e '
152
- + 'ALATT van → a read jelzi, hogy valószínűleg nincs lefedve (north-star). Modell-függő (qwen3 '
153
- + 'zaj-padló ~0.44); állítsd a provider/modell szerint.',
166
+ type: 'number', default: 0.60, min: 0.0, max: 1.0, levels: ALL_LEVELS,
167
+ // PER-TÁR kalibráció (2026-08-14 MÉRÉS): a tárak score-tartománya ELTÉR a `codebase`/`documents` szimbólum-
168
+ // és cím-egyezései magasabbra futnak (top 0.75-0.84), a `rules`/`memory` prózai egyezései alacsonyabbra
169
+ // (top 0.64-0.66). Egységes küszöbbel az egyik tár túl engedékeny, a másik túl szigorú lenne.
170
+ defaultsByTable: {
171
+ [fam_table_type_enum_1.FAM_Table.codebase]: 0.65,
172
+ [fam_table_type_enum_1.FAM_Table.documents]: 0.65,
173
+ // A `rules` korpusz ANGOL próza, a kérdések viszont jórészt MAGYARUL érkeznek → a cosine
174
+ // rendszeresen alacsonyabb. MÉRVE (8 valós, JÓ query): a 0.60-as floor mellett 4/8 query azt
175
+ // jelentette volna, hogy „0 magabiztos találat" — HOLOTT a helyes szabály a #1 volt. 0.52-nél 1/8.
176
+ [fam_table_type_enum_1.FAM_Table.rules]: 0.52,
177
+ },
178
+ description: 'Confidence-padló a NYERS cosine score-on: a relevancia-jelzések (`totalRelevant` / dense / '
179
+ + 'suggestions) alapja. **MÉRVE (2026-08-14):** a korábbi `0.5` a `memory` táron 500 tételt jelölt '
180
+ + '„magabiztosan relevánsnak" (a valódi egyezések 0.60 FÖLÖTT voltak) — a szám így nem információ volt, '
181
+ + 'hanem zaj, és a dense-jelzés is folyamatosan tüzelt. A `0.60` (codebase/documents `0.65`) a mért '
182
+ + 'eloszlásból jön. FONTOS: ez a jelzéseket kalibrálja, NEM dobja el a találatokat — a hit-eket a '
183
+ + '`read.minScore` szűri (best-effort recall marad).',
154
184
  },
155
185
  'read.importChunkWeight': {
156
186
  type: 'number', default: 0.4, min: 0.0, max: 1.0, levels: ALL_LEVELS,
@@ -160,6 +190,17 @@ exports.CONFIG_CATALOG = {
160
190
  + 'importChunkWeight` a definíció-chunkok ALÁ rangsorolja őket. READ-time (nincs re-embed); a nyers '
161
191
  + 'cosine score (relevancia-jelzések) NEM változik, csak a ranking. 1 = nincs csökkentés.',
162
192
  },
193
+ 'read.weightInfluence': {
194
+ type: 'number', default: 0.06, min: 0.0, max: 1.0, levels: ALL_LEVELS,
195
+ description: 'A statikus `weight` BEFOLYÁSA a rangsorra (2026-08-14 lelet). A `finalScore` eddig LINEÁRISAN '
196
+ + 'szorzott a weight-tel, a koszinusz viszont csak ~0.3-0.8 között mozog — ezért egy `weight:5` tétel '
197
+ + 'MATEMATIKAILAG VERHETETLEN volt: mérve a `rules` táron 2 db w5-ös bejegyzés ült MINDEN szabály-keresés '
198
+ + 'tetején (score 0.519 × 5 = 2.595 vs. a helyes találat 0.621 × 1 = 0.742). Az effektív súly mostantól '
199
+ + '`1 + weightInfluence × (weight - 1)` — vagyis a weight DÖNTETLENT BONT (a kurált tartalom felé), de NEM '
200
+ + 'írja felül a szemantikai relevanciát. `0.12` → w5 ≈ 1.48 · w3 ≈ 1.24 · w2 ≈ 1.12. `1.0` = a régi, '
201
+ + 'lineáris viselkedés; `0` = a weight teljesen figyelmen kívül. Az azonos súlyú tételek egymáshoz '
202
+ + 'viszonyított sorrendje VÁLTOZATLAN (a tár-belső rangsort nem érinti).',
203
+ },
163
204
  'read.importSourceWeight': {
164
205
  type: 'number', default: 0.85, min: 0.0, max: 1.0, levels: ALL_LEVELS,
165
206
  description: 'Az import-FORRÁSÚ (`source.type:"import"`, pl. claude-mem-import) entry-k read-idejű rangsor-'
@@ -177,16 +218,14 @@ exports.CONFIG_CATALOG = {
177
218
  + '(régi viselkedés). Default 0.6.',
178
219
  },
179
220
  'read.scanSummaryWeight': {
180
- type: 'number', default: 1.0, min: 0.0, max: 10.0, levels: ALL_LEVELS,
181
- description: 'A `source.type:"scan-summary"` (auto-generált projekt-összefoglaló: `project-identity` + '
182
- + '`fs-summary`) chunkok EFFEKTÍV statikus weight-SAPKÁJA read-időben (2026-07-26 dispatch-eval): e '
183
- + 'generikus entry-k `weight:3` (project-identity) / `weight:2` (fs-summary) `finalScore ~2.2` még '
184
- + 'lexicalScore 0-0.2 mellett is elnyomta a szemantikailag+lexikailag relevánsabb doksikat. E cap a '
185
- + 'scan-summary effektív weight-jét `min(weight, scanSummaryWeight)`-re szorítja (factor = cap/weight), '
186
- + 'hogy a STATIKUS weight NE tudja legyőzni a relevanciát. `1.0` = tiszta relevancia-rangsor (nincs statikus '
187
- + 'boost); az identity-jellegű query-ken a summary a MAGAS cosine-ja miatt így is felszínre kerül (nincs '
188
- + 'regresszió). READ-time (nincs re-embed); a nyers cosine + a `weight` DTO-mező NEM változik, csak a ranking. '
189
- + 'A stored weight ALÁ csökkenteni is lehet (<1) az agresszívabb leszorításhoz; ≥ stored weight = kikapcsolva.',
221
+ type: 'number', default: 0.8, min: 0, max: 10, levels: ALL_LEVELS,
222
+ description: 'A `scan-summary` forrasu bejegyzesek (fs-summary konyvtar-listak) EFFEKTIV suly-plafonja. '
223
+ + 'Ezek atlagosan ~22 000 karakteres fajlnev-listak `weight: 2`-vel, es mivel egy repo MINDEN fajlnevet '
224
+ + 'tartalmazzak, gyakorlatilag BARMIRE illeszkednek. Mert kalibracio (2026-08-22, 12 zaj- + 3 jogos '
225
+ + 'navigacios query): 1.0 eseten a top-K slotok 35%-a fs-summary szemet volt (2 query-n a TOP-1 is); '
226
+ + '0.9 eseten 20%; 0.8 eseten 5% UGY, hogy a jogos "mi van ebben a repoban" kerdes tovabbra is '
227
+ + '3/3-ban megtalalja; 0.7 alatt a zaj mar 0%, DE a jogos navigacio is romlani kezd (2/3, majd 1/3). '
228
+ + 'Ezert 0.8 az optimum. A cap a TEMPERALT (effectiveWeight) sulyra hat.',
190
229
  },
191
230
  'read.denseResultRatio': {
192
231
  type: 'number', default: 2.0, min: 1.0, max: 50.0, levels: ALL_LEVELS,
@@ -33,7 +33,7 @@ class FAM_AgentGuidance_Util {
33
33
  /** A `read` tool minimál, helyes hívása (a leggyakoribb szándék). */
34
34
  static READ_EXAMPLE = '{"queries":[{"tables":["knowledge"],"query":"amit keresel","topK":5}]}';
35
35
  /** A `write` tool minimál, helyes hívása. */
36
- static WRITE_EXAMPLE = '{"operation":"create","table":"memory","content":"a megjegyzendő tudás","scopePath":[{"layer":"project","name":"<projekt>"}]}';
36
+ static WRITE_EXAMPLE = '{"operation":"create","table":"memory","content":"a megjegyzendő tudás","scopePath":[{"layer":"project","rawName":"<projekt>"}]}';
37
37
  /**
38
38
  * A JSON-parse hiba KONKRÉT okának felismerése a nyers body-ból (PURE). A body-parser üzenete önmagában
39
39
  * (`Expected property name or '}' at position 1`) egy agentnek keveset mond — a tényleges minta viszont
@@ -34,16 +34,26 @@ class FAM_McpBridge_Util {
34
34
  handler: async (args) => {
35
35
  let result = await client.post(`/api/${tool.name}`, args ?? {});
36
36
  // ÖNHEAL (user-FR 2026-06-22): ha a primary időközben leesett (`unreachable`), NE bukjunk némán —
37
- // automatikusan felhúzzuk a leválasztott primary-t (`ensureRunning`) és EGYSZER újrapróbáljuk. Ha a
38
- // primary épp bootol (pool-hidratálás), tiszta „indul, próbáld újra" hibát adunk (a következő hívás megy).
37
+ // automatikusan felhúzzuk a leválasztott primary-t (`ensureRunning`) és EGYSZER újrapróbáljuk.
39
38
  if (!result.ok && result.unreachable) {
40
39
  const ensured = await serve_command_1.FAM_Serve_Util.ensureRunning();
41
40
  if (ensured.running) {
42
41
  result = await client.post(`/api/${tool.name}`, args ?? {});
43
42
  }
44
43
  else if (ensured.reason === 'boot-timeout') {
45
- throw new Error('FAM-MCP-BRIDGE-002: A FAM primary most indul (pool-hidratálás folyamatban) — '
46
- + 'próbáld újra pár másodperc múlva. (Az auto-indítás már elindult, leválasztva.)');
44
+ throw new Error(`FAM-MCP-BRIDGE-002: ${await FAM_McpBridge_Util.bootHint(client)}`);
45
+ }
46
+ else {
47
+ // A leválasztott indítás NEM sikerült (`no-entry` / `spawn-failed` / `remote-unreachable`).
48
+ // Eddig ez a generikus „a REST-hívás sikertelen" ágra esett, amiből az agent NEM tudhatta meg,
49
+ // hogy egyáltalán KELL egy szervert indítani, se azt, MIVEL (2026-08-22 owner-lelet).
50
+ throw new Error(`FAM-MCP-BRIDGE-003: A FAM primary NEM fut, és az automatikus indítás sem `
51
+ + `sikerült (ok: ${ensured.reason}; cél: ${ensured.url}). A FAM-nak szüksége van egy futó `
52
+ + 'primary-ra — indítsd el KÉZZEL, egyszer, és utána minden MCP-hívás működik: '
53
+ + '`fam serve` (állandó, leválasztott szerver; a session-öket túléli). '
54
+ + 'Diagnózis: `fam doctor`, vagy `GET http://127.0.0.1:39265/api/health`. '
55
+ + 'Ha a port foglalt egy fél-halott processzel, azt előbb állítsd le. '
56
+ + 'Addig NE jelentsd a FAM-ot elérhetetlennek — a REST-felület ugyanezen a porton él.');
47
57
  }
48
58
  }
49
59
  if (!result.ok) {
@@ -59,5 +69,36 @@ class FAM_McpBridge_Util {
59
69
  }
60
70
  await adapter.startStdio();
61
71
  }
72
+ /**
73
+ * A „most bootol" hibaüzenet ÉLŐ boot-állapottal. Mért indok (2026-08-22): a korábbi szöveg annyit mondott, hogy
74
+ * „próbáld újra pár másodperc múlva" — miközben a hideg boot MÉRVE ~70–120 s (~338k vektor / ~9 GB). Az agent ezt
75
+ * szó szerint vette, pár másodpercenként újrapróbált, végig bukott, és ebből arra jutott, hogy a FAM hibás. A
76
+ * „pár másodperc" tehát nem pontatlanság volt, hanem AKTÍVAN félrevezető: pont abba a mintába lökte, ami nem
77
+ * segíthet. Ezért az üzenet most (a) megmondja, hol tart a boot, (b) őszinte nagyságrendet ad, (c) a
78
+ * `/api/health`-re irányít a lekérdezés-ismételgetés HELYETT (`core-error-debuggable`).
79
+ */
80
+ static async bootHint(client) {
81
+ // A `client.get` NEM dob a nem-elérhető szerverre, hanem `ok:false`-t ad — ezért az „még nem lekérdezhető"
82
+ // ágat EXPLICITEN kezelni kell, különben a legkorábbi (leghidegebb) hívásnál épp a helyzet-információ marad ki.
83
+ let progress = ' (A boot-állapot még nem lekérdezhető — a REST-felület sem áll fel; ez a boot legelső '
84
+ + 'másodperceiben normális.)';
85
+ try {
86
+ const health = await client.get('/api/health');
87
+ const hydration = health.ok ? health.data?.hydration : undefined;
88
+ if (hydration) {
89
+ progress = ` Boot-állapot: ${hydration.hydratedTables ?? '?'}/${hydration.totalTables ?? '?'} tár kész`
90
+ + `${hydration.pendingTables?.length ? ` — hátra van: ${hydration.pendingTables.join(', ')}` : ''}.`;
91
+ }
92
+ }
93
+ catch {
94
+ // marad az alapértelmezett „még nem lekérdezhető" szöveg
95
+ }
96
+ return 'A FAM primary MOST INDUL, a vektor-pool hidratál — az auto-indítás már elfutott (leválasztva), '
97
+ + `tehát NEKED nem kell szervert indítanod.${progress} `
98
+ + 'A hideg boot MÉRVE ~2 perc (~338 ezer vektor, ~9 GB), NEM „pár másodperc". '
99
+ + 'NE ismételgesd ezt a hívást — nézd a készenlétet: `GET http://127.0.0.1:39265/api/health` '
100
+ + '(`ready` + `hydration.pendingTables`), és addig dolgozz máson. '
101
+ + 'Ha előre el akarod kerülni ezt az ablakot, indítsd a FAM-ot a session ELEJÉN: `fam serve`.';
102
+ }
62
103
  }
63
104
  exports.FAM_McpBridge_Util = FAM_McpBridge_Util;
@@ -103,9 +103,20 @@ class FAM_HydrationCoordinator_ControlService {
103
103
  const limitMs = timeoutMs ?? this.waitTimeoutMs;
104
104
  const ready = await this.raceWithTimeout(entry.promise, limitMs);
105
105
  if (!ready) {
106
- throw this.hydrationError(table, `A tár hidratálása ${Math.round(limitMs / 1000)}s alatt nem fejeződött be `
107
- + '(a pool-hidratálás folyamatban boot-kor a nagy tárak percekig tarthatnak). Próbáld újra pár '
108
- + 'másodperc múlva; a következő hívás már a kész poolt találja.');
106
+ // A hiba a TELJES boot-képet viszi (mely tárak készek / melyik van hátra). Mért indok (2026-08-22):
107
+ // e nélkül a hívó csak egy csupasz timeoutot lát, és nem tudja megkülönböztetni a „most bootol" és a
108
+ // „elromlott" esetet egy agent emiatt háromszor ismételte ugyanazt a lekérdezést, majd HIBÁSAN arra
109
+ // jutott, hogy a FAM végig egészséges volt, és csak a saját kliens-plafonja kevés (`core-error-debuggable`).
110
+ const shot = this.snapshot();
111
+ const readyTables = shot.filter((s) => s.state === 'ready').map((s) => s.table);
112
+ const waiting = shot.filter((s) => s.state !== 'ready').map((s) => `${s.table}:${s.state}`);
113
+ throw this.hydrationError(table, `A tár hidratálása ${Math.round(limitMs / 1000)}s alatt nem fejeződött be. `
114
+ + `Boot-állapot: ${readyTables.length}/${shot.length} tár kész`
115
+ + `${waiting.length ? ` — hátra van: ${waiting.join(', ')}` : ''}. `
116
+ + 'Ez NEM hiba és NEM a te hívásod mérete: a pool-hidratálás fut (hideg boot-kor a nagy tárak '
117
+ + 'ÖSSZESEN percekig tartanak). NE ismételd ugyanazt a lekérdezést — várd meg a készenlétet a '
118
+ + '`GET /api/health` `ready` / `hydration.pendingTables` mezőin, VAGY dolgozz addig máson. '
119
+ + 'A várakozási korlát a `read.hydrationWaitMs` configgal állítható.');
109
120
  }
110
121
  }
111
122
  /** Egy tár aktuális állapota (diag / health / teszt). */
@@ -379,6 +379,21 @@ class FAM_ScanJob_ControlService {
379
379
  return resumed;
380
380
  }
381
381
  /** A default per-projekt futtató: a meglévő `scan-project` write-handler az operation-queue-n át. */
382
+ /**
383
+ * Egy scan-cel kimenetenek OSZTALYOZASA (pure) — a nema elavulas elleni kapu.
384
+ *
385
+ * Mert lelet (2026-08-22): a heti re-scan HAROM egymast koveto heten (aug 3/10/17) bukott a `documentations` es az
386
+ * `OGS-projects\documents` mappakra, mert azokat atneveztek (`fdp-documentations` / `ogs-documents`), de a job
387
+ * cel-listaja a REGI utvonalat vitte tovabb. A runner viszont mindig `ok`-ot adott vissza, ha a hivas nem dobott —
388
+ * igy a job `53/53 ok`-kent zarult, es a FAM KET HONAPIG a juniusi doktrinat szolgalta ki ugy, hogy ez sehol nem
389
+ * latszott. A `filesProcessed: 0` ugyanezt jelenti egy letezo utvonalon (rossz include/exclude vagy tul ago ignore).
390
+ */
391
+ static classifyTargetOutcome(set) {
392
+ if (!set.pathExists) {
393
+ return 'missing-path';
394
+ }
395
+ return set.filesProcessed ? 'ok' : 'zero-files';
396
+ }
382
397
  async defaultRunProject(spec) {
383
398
  const startedAt = Date.now();
384
399
  // Per-target scope: az EXPLICIT `spec.scopePath` nyer (heterogén „scan-all"-hoz, pl. a memóriám root-szintű
@@ -401,11 +416,43 @@ class FAM_ScanJob_ControlService {
401
416
  exclude: spec.exclude,
402
417
  },
403
418
  };
419
+ // ELO-ELLENORZES (2026-08-22, mert lelet): a heti re-scan HAROM egymast koveto heten (aug 3/10/17) NEMAN
420
+ // bukott a `documentations` / `OGS-projects\documents` mappakra — azokat atneveztek (`fdp-documentations` /
421
+ // `ogs-documents`), de a job-lista a REGI utvonalat vitte tovabb. A `defaultRunProject` viszont feltetel nelkul
422
+ // `ok`-ot adott vissza, ezert a job `53/53 ok`-kent zarult, es a FAM ket honapig a JUNIUSI doktrinat szolgalta ki
423
+ // ugy, hogy azt senki nem lathatta. A nem letezo cel-utvonal ezentul HANGOS hiba, nem zold pipa.
424
+ if (FAM_ScanJob_ControlService.classifyTargetOutcome({ pathExists: (0, fs_1.existsSync)(spec.path) }) === 'missing-path') {
425
+ return {
426
+ project: spec.project, path: spec.path, status: 'error',
427
+ seconds: Math.round((Date.now() - startedAt) / 1000),
428
+ error: {
429
+ message: `A scan cel-utvonala NEM LETEZIK: '${spec.path}'. A mappat valoszinuleg atneveztek vagy `
430
+ + 'athelyeztek — javitsd a scan-job cel-listajat, kulonben ez a forras NEMAN elavul.',
431
+ },
432
+ };
433
+ }
404
434
  try {
405
435
  const output = await fam_operation_queue_service_1.FAM_OperationQueue_Service.getInstance()
406
436
  .enqueue(() => fam_write_tool_service_1.FAM_WriteTool_Service.getInstance().handle(input));
407
437
  const scan = output.scan;
408
438
  const verdicts = scan?.verdicts;
439
+ // NULLA feldolgozott fajl letezo utvonalon: a cel nem jarult hozza az indexhez (rossz include/exclude,
440
+ // ures mappa vagy tul ago ignore-minta). Ez NEM siker — kulonben ugyanaz a nema elavulas all elo, csak
441
+ // mas okbol. Hangos hiba, hogy a heti riport ranezesre megmutassa.
442
+ const outcome = FAM_ScanJob_ControlService.classifyTargetOutcome({
443
+ pathExists: true, filesProcessed: scan?.filesProcessed,
444
+ });
445
+ if (outcome === 'zero-files') {
446
+ return {
447
+ project: spec.project, path: spec.path, status: 'error',
448
+ filesProcessed: 0, chunks: scan?.chunks, verdicts: verdicts,
449
+ seconds: Math.round((Date.now() - startedAt) / 1000),
450
+ error: {
451
+ message: `A scan NULLA fajlt dolgozott fel a letezo '${spec.path}' utvonalon — a cel nem `
452
+ + 'jarult hozza az indexhez (ellenorizd az include/exclude + .famignore mintakat).',
453
+ },
454
+ };
455
+ }
409
456
  return {
410
457
  project: spec.project, path: spec.path, status: 'ok',
411
458
  filesProcessed: scan?.filesProcessed, chunks: scan?.chunks, verdicts: verdicts,
@@ -33,7 +33,10 @@ class FAM_ReadResponseShape_Util {
33
33
  table: hit.table,
34
34
  // ÚTVONAL-SSOT: a `sourceFilePath` már a hívó `basePath`-jához igazított (rebaseToBasePath), különben az
35
35
  // abszolút út — így MINDIG feloldható. A régi mezőket lentebb megtartjuk (kompatibilitás).
36
- path: hit.sourceFilePath ?? hit.absolutePath ?? null,
36
+ // Ha nincs fájl-forrás (API-n írt bejegyzés: manual/agent write), a `path` NEM maradhat néma `null` —
37
+ // az az agentnek zsákutca („itt egy találat, de nem tudom, hol van"). Helyette LOCATOR-t adunk, amivel
38
+ // a bejegyzés visszakereshető: `fam:<tár>/<id>` (2026-08-14 elő-próbálgatás lelete).
39
+ path: hit.sourceFilePath ?? hit.absolutePath ?? `fam:${hit.table}/${hit.id}`,
37
40
  score: round(hit.score),
38
41
  finalScore: round(hit.finalScore),
39
42
  };
@@ -124,12 +127,24 @@ class FAM_ReadResponseShape_Util {
124
127
  + 'a javasolt út — a chat-választ nem terheli).');
125
128
  }
126
129
  if (set.totalRelevant > set.shown * 3 && !set.hasScopeFilter) {
127
- hints.push('CÉLZOTTABBAN: szűkíts `scope`-pal (pl. `{"layer":"project","name":"<projekt>"}`), `tags`-szel '
130
+ hints.push('CÉLZOTTABBAN: szűkíts `scope`-pal (pl. `{"layer":"project","rawName":"<projekt>"}`), `tags`-szel '
128
131
  + 'vagy `kind`-dal — a sok találat általában túl tág kérdést jelez.');
129
132
  }
130
133
  if (!set.totalRelevant) {
131
- hints.push('NINCS magabiztos találat: próbálj más megfogalmazást/kulcsszavakat, vagy emeld a `topK`-t; '
132
- + 'a `minScore: 0` a leggyengébb tippeket is visszaadja.');
134
+ // SZŰRŐ-SPECIFIKUS diagnózis (2026-08-14 battery-lelet): ha VAN aktív szűrő és NULLA a találat, akkor
135
+ // nagy eséllyel a SZŰRŐ ölte meg a keresést, nem a query. Korábban ilyenkor is azt tanácsoltuk, hogy
136
+ // „próbálj más megfogalmazást" — ez FÉLREVEZETŐ volt (mérve: ugyanaz a query szűrő nélkül 5 találatot
137
+ // adott). Az agentet a TÉNYLEGES okhoz kell terelni.
138
+ if (set.hasScopeFilter && !set.shown) {
139
+ hints.push('NULLA találat AKTÍV SZŰRŐVEL — nagy eséllyel a szűrő a szűk keresztmetszet, nem a kérdés. '
140
+ + 'Próbáld a query-t szűrő NÉLKÜL (`scopeFilter`/`tagFilter`/`kindFilter` elhagyásával); ha úgy '
141
+ + 'vannak találatok, a szűrő értéke hibás vagy nem létezik (a `tags`/`kind` szabad szövegűek, a '
142
+ + 'scope-hoz a `{"layer":"…","rawName":"…"}` alak kell).');
143
+ }
144
+ else {
145
+ hints.push('NINCS magabiztos találat: próbálj más megfogalmazást/kulcsszavakat, vagy emeld a `topK`-t; '
146
+ + 'a `minScore: 0` a leggyengébb tippeket is visszaadja.');
147
+ }
133
148
  }
134
149
  return hints;
135
150
  }
@@ -237,7 +237,10 @@ class FAM_ReadTool_Service {
237
237
  });
238
238
  }
239
239
  for (const query of input.queries) {
240
- if (!query || !Array.isArray(query.tables) || !query.tables.length || !query.query) {
240
+ // A `.trim()` SZANDEKOS (2026-08-22 robusztussag-lelet): a puszta `!query.query` csak az URES stringet
241
+ // fogta meg, ezert a csupa-szokoz query ATMENT a validacion, es a vektor-kereses ertelmetlen zajt adott
242
+ // vissza (egy meresen ~13k token egyetlen ertelmetlen keresesre).
243
+ if (!query || !Array.isArray(query.tables) || !query.tables.length || !query.query?.trim()) {
241
244
  throw fam_error_factory_util_1.FAM_Error_Util.create({
242
245
  errorCode: error_codes_const_1.FAM_ERROR_CODES.valReadEmptyOrReference,
243
246
  message: 'Minden `read` query-nek legalább 1 `tables` elemet és egy nem-üres `query` '
@@ -246,6 +249,44 @@ class FAM_ReadTool_Service {
246
249
  context: { operation: 'read-validate' },
247
250
  });
248
251
  }
252
+ // ISMERETLEN TÁR (2026-08-14 battery-lelet): eddig egy ELGÉPELT tárnév NÉMÁN 0 találatot adott (HTTP 200)
253
+ // — az agent ebből azt a HAMIS következtetést vonja le, hogy „nincs ilyen tudás". Ez a legrosszabb hiba-fajta:
254
+ // csendes, téves válasz. Mostantól EXPLICIT hiba, a MEGENGEDETT tárak felsorolásával.
255
+ const known = Object.values(fam_table_type_enum_1.FAM_Table);
256
+ const unknown = query.tables.filter((table) => !known.includes(table));
257
+ if (unknown.length) {
258
+ throw fam_error_factory_util_1.FAM_Error_Util.create({
259
+ errorCode: error_codes_const_1.FAM_ERROR_CODES.valReadEmptyOrReference,
260
+ message: `Ismeretlen tár: ${unknown.map((t) => `'${t}'`).join(', ')}. `
261
+ + `Megengedett: ${known.filter((t) => t !== fam_table_type_enum_1.FAM_Table.reference).join(' | ')}. `
262
+ + '(Elgépelés? A `capabilities` action:"list" felsorolja a tárakat és a pontos sémákat.)',
263
+ issuer: this.issuer,
264
+ context: { operation: 'read-validate' },
265
+ });
266
+ }
267
+ // topK/minScore TARTOMÁNY (2026-08-14 battery-lelet): a `topK: -5` eddig egy NYERS TypeError-t okozott
268
+ // (`Cannot read properties of undefined (reading 'finalScore')`) — kezeletlen összeomlás, kriptikus üzenettel.
269
+ // A FELSO hatar is ELLENORZOTT (2026-08-22): a hibauzenet eddig is „1 es 200 kozotti" erteket igert, de a
270
+ // kod a `topK: 10000`-t elfogadta — a szerzodes es a viselkedes szetcsuszott, es a hivo context-bombat kapott.
271
+ if (query.topK !== undefined
272
+ && (!Number.isFinite(query.topK) || query.topK < 1 || query.topK > retrieval_1.MAX_PAGE_SIZE)) {
273
+ throw fam_error_factory_util_1.FAM_Error_Util.create({
274
+ errorCode: error_codes_const_1.FAM_ERROR_CODES.valReadEmptyOrReference,
275
+ message: `A \`topK\` 1 és ${retrieval_1.MAX_PAGE_SIZE} közötti egész szám kell legyen `
276
+ + `(kapott: ${String(query.topK)}). Hagyd el a mezőt a tár-alapú default-hoz.`,
277
+ issuer: this.issuer,
278
+ context: { operation: 'read-validate' },
279
+ });
280
+ }
281
+ if (query.minScore !== undefined && (!Number.isFinite(query.minScore) || query.minScore < 0 || query.minScore > 1)) {
282
+ throw fam_error_factory_util_1.FAM_Error_Util.create({
283
+ errorCode: error_codes_const_1.FAM_ERROR_CODES.valReadEmptyOrReference,
284
+ message: `A \`minScore\` 0 és 1 közötti szám kell legyen (kapott: ${String(query.minScore)}) — `
285
+ + 'ez a NYERS koszinusz-küszöb. `0` = mindent visszaad (best-effort recall).',
286
+ issuer: this.issuer,
287
+ context: { operation: 'read-validate' },
288
+ });
289
+ }
249
290
  if (query.tables.includes(fam_table_type_enum_1.FAM_Table.reference)) {
250
291
  throw fam_error_factory_util_1.FAM_Error_Util.create({
251
292
  errorCode: error_codes_const_1.FAM_ERROR_CODES.valReadEmptyOrReference,
@@ -278,6 +319,8 @@ class FAM_ReadTool_Service {
278
319
  kindFilter: query.kindFilter,
279
320
  excludeIds: query.excludeIds,
280
321
  depth: query.depth,
322
+ // Per-query tartalom-kapcsolo atvezetese (2026-08-14 lelet: eddig itt VESZETT EL).
323
+ includeContent: query.includeContent,
281
324
  };
282
325
  }
283
326
  }
@@ -163,6 +163,29 @@ class FAM_WriteTool_Service {
163
163
  context: { operation: 'write-validate', table: input.table },
164
164
  });
165
165
  }
166
+ // WEIGHT / IMPORTANCE TARTOMANY (2026-08-22 robusztussag-meres): a `weight: -5` eddig NEMAN atment
167
+ // (`warnings: []`), es a bejegyzes ezzel VEGLEG visszakereshetetlenne valt — a `finalScore = score x weight`
168
+ // negativba fordul, igy a talalat SOHA nem rangsorolhat, meg a sajat egyedi markerere + tagjere sem. Az agent
169
+ // ebbol azt latja, hogy „az iras sikerult, de a tartalom nincs meg" — a legrosszabb fajta nema adatvesztes.
170
+ if (input.weight !== undefined && (!Number.isFinite(input.weight) || input.weight <= 0)) {
171
+ throw fam_error_factory_util_1.FAM_Error_Util.create({
172
+ errorCode: error_codes_const_1.FAM_ERROR_CODES.valWriteMissingField,
173
+ message: `A \`weight\` POZITIV szam kell legyen (kapott: ${String(input.weight)}). A rangsor `
174
+ + '`score x weight` szorzat, ezert a 0 vagy negativ suly a bejegyzest VEGLEG '
175
+ + 'visszakereshetetlenne teszi. Alap: 1 (coding_patterns: 5).',
176
+ issuer: this.issuer,
177
+ context: { operation: 'write-validate', table: input.table },
178
+ });
179
+ }
180
+ if (input.importance !== undefined
181
+ && (!Number.isFinite(input.importance) || input.importance < 0 || input.importance > 1)) {
182
+ throw fam_error_factory_util_1.FAM_Error_Util.create({
183
+ errorCode: error_codes_const_1.FAM_ERROR_CODES.valWriteMissingField,
184
+ message: `Az \`importance\` 0 es 1 kozotti szam kell legyen (kapott: ${String(input.importance)}).`,
185
+ issuer: this.issuer,
186
+ context: { operation: 'write-validate', table: input.table },
187
+ });
188
+ }
166
189
  const needsTarget = input.operation === fam_mcp_interface_1.FAM_WriteOperation.update || input.operation === fam_mcp_interface_1.FAM_WriteOperation.delete;
167
190
  if (needsTarget && (!input.target || !input.target.by || !input.target.value)) {
168
191
  throw fam_error_factory_util_1.FAM_Error_Util.create({
@@ -24,19 +24,44 @@ class FAM_LexicalMatch_Util {
24
24
  'az', 'és', 'es', 'hogy', 'van', 'egy', 'nem', 'igen', 'de', 'mert', 'ami', 'amit', 'ez', 'azt', 'volt',
25
25
  'lesz', 'kell', 'meg', 'el', 'be', 'ki', 'le', 'fel', 'rá', 'ra', 'mi', 'te', 'ő', 'mit', 'hol',
26
26
  ]);
27
+ /** A magyar ragok HOSSZ szerint csökkenő sorrendben (a leghosszabb egyezés nyer — `-ból` előbb, mint `-ó`). */
28
+ static HU_SUFFIXES = [
29
+ 'ban', 'ben', 'ból', 'ből', 'nak', 'nek', 'val', 'vel', 'hoz', 'hez', 'höz', 'ról', 'ről', 'tól', 'től',
30
+ 'ot', 'et', 'öt', 'at', 'ok', 'ek', 'ök', 'ak', 'ra', 're', 'ig', 'ba', 'be', 'on', 'en', 'ön', 'an',
31
+ ];
32
+ /** A tő MINIMÁLIS hossza tövezés után (a túl-tövezés hamis egyezéseket hozna). */
33
+ static MIN_STEM_LEN = 4;
27
34
  /** Tokenizálás: kisbetűsít, nem-alfanumerikuson tör, stopword + rövid token kihagyva, DEDUP (Set). */
28
35
  static tokenize(text) {
29
36
  const terms = new Set();
30
37
  if (!text) {
31
38
  return terms;
32
39
  }
33
- for (const raw of text.toLowerCase().split(/[^a-z0-9]+/)) {
34
- if (raw.length >= FAM_LexicalMatch_Util.MIN_TOKEN_LEN && !FAM_LexicalMatch_Util.STOPWORDS.has(raw)) {
35
- terms.add(raw);
40
+ // ÉKEZET-JAVÍTÁS (2026-08-14 lelet): a korábbi `[^a-z0-9]+` split a magyar ékezetes betűket ELVÁLASZTÓNAK
41
+ // vette, ezért minden ékezetes szó SZÉTESETT („szabály" „szab" + „ly", „mérés" → „m" + „r" + „s").
42
+ // Így a magyar query-k lexikai jele gyakorlatilag ELVESZETT.
43
+ for (const raw of text.toLowerCase().split(/[^a-z0-9áéíóöőúüű]+/)) {
44
+ if (raw.length < FAM_LexicalMatch_Util.MIN_TOKEN_LEN || FAM_LexicalMatch_Util.STOPWORDS.has(raw)) {
45
+ continue;
36
46
  }
47
+ terms.add(raw);
37
48
  }
38
49
  return terms;
39
50
  }
51
+ /**
52
+ * Magyar toldalék levágása (PURE) — a tő, ha a levágás UTÁN is értelmes hosszúságú marad, különben `undefined`.
53
+ * A leggyakoribb rag-osztályok: tárgyeset (`-t`/`-ot`/`-et`/`-öt`/`-at`), többes (`-ok`/`-ek`/`-ök`/`-ak`),
54
+ * hely-/eszközhatározók (`-ban`/`-ben`/`-ból`/`-ből`/`-ra`/`-re`/`-val`/`-vel`/`-nak`/`-nek`/`-hoz`/`-hez`/`-ig`).
55
+ * A `MIN_STEM_LEN` védi a túl-tövezéstől (pl. `este` → `est` nem keletkezik hamis egyezésként).
56
+ */
57
+ static stripHungarianSuffix(token) {
58
+ for (const suffix of FAM_LexicalMatch_Util.HU_SUFFIXES) {
59
+ if (token.length - suffix.length >= FAM_LexicalMatch_Util.MIN_STEM_LEN && token.endsWith(suffix)) {
60
+ return token.slice(0, token.length - suffix.length);
61
+ }
62
+ }
63
+ return undefined;
64
+ }
40
65
  /**
41
66
  * Lexikai átfedés-score (0..1): a `queryTerms` hányad része van jelen az entry kereshető szövegében.
42
67
  * `content`-egyezés = base-átfedés; `headingPath`/`sourceFilePath`-egyezés = + `TITLE_PATH_BONUS` súly (1-re vágva).
@@ -52,8 +77,14 @@ class FAM_LexicalMatch_Util {
52
77
  let baseMatched = 0;
53
78
  let titleMatched = 0;
54
79
  for (const term of queryTerms) {
55
- const inTitlePath = titlePathTerms.has(term);
56
- if (contentTerms.has(term) || inTitlePath) {
80
+ // MAGYAR TOLDALÉK-ILLESZTÉS (2026-08-14): a magyar az ANGOL szakszavakat is ragozza („loopot",
81
+ // „scriptet", „branchról"), így a nyers `loopot` SOHA nem egyezett a dokumentum `loop` szavával — mérve:
82
+ // a „mikor kell review loopot futtatni" query 5 tokenjéből EGY egyezett. A tő ILLESZTÉSI SEGÉD, NEM külön
83
+ // query-term: nem növeli a nevezőt (különben az angol szavak téves tövezése — `convention` → `conventi` —
84
+ // HÍGÍTANÁ a score-t), csak egy MÁSODIK esélyt ad ugyanannak a tokennek az egyezésre.
85
+ const stem = FAM_LexicalMatch_Util.stripHungarianSuffix(term);
86
+ const inTitlePath = titlePathTerms.has(term) || (!!stem && titlePathTerms.has(stem));
87
+ if (contentTerms.has(term) || (!!stem && contentTerms.has(stem)) || inTitlePath) {
57
88
  baseMatched++;
58
89
  }
59
90
  if (inTitlePath) {
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAM_ResultDiversity_Util = void 0;
4
+ /**
5
+ * `FAM_ResultDiversity_Util` — a visszaadott top-K **információ-sűrűségét** védi. Két MÉRT hibát javít
6
+ * (dogfood-mérés, 2026-08-22):
7
+ *
8
+ * 1. **Chunk-elárasztás:** a top-K slotok **44%-a** (11/25 mérve) UGYANANNAK a fájlnak egy másik chunkja volt —
9
+ * a „polling tilalom" query-nél a TELJES top-5 EGYETLEN fájl darabjaiból állt. Az agent így egy dokumentumot
10
+ * kap ötször, öt különböző válasz-jelölt helyett.
11
+ * 2. **Tár-temetés:** több-táras query-ben a `rules` **0/3** arányban tűnt el a `documents` mögött (kontrollált
12
+ * mérés: ugyanaz a query csak-`rules`-szal AZONNAL hozta a kanonikus szabályt) — a bőbeszédűbb tár egyszerűen
13
+ * kiszorítja a tömörebbet, és az agent azt hiszi, nincs rá szabály.
14
+ *
15
+ * **Sosem DOBUNK el találatot** (a recall szent): a levágott chunkok a lista VÉGÉRE kerülnek (demote, nem drop),
16
+ * így a `totalRelevant` és a `truncated` statisztika érintetlen marad. A `pinned` (kód-exact citáció) hitek
17
+ * mozdíthatatlanok — az F3-1 garancia elsőbbséget élvez a diverzitással szemben.
18
+ */
19
+ class FAM_ResultDiversity_Util {
20
+ /** Legfeljebb ennyi promóciót engedünk a tár-kvótának, hogy a rangsor ne forduljon fel (window fele). */
21
+ static MAX_PROMOTION_RATIO = 0.5;
22
+ /**
23
+ * A fájl-identitás kulcsa. A scan-chunkok `absolutePath`-ja a kanonikus kulcs; ha nincs (memory/knowledge
24
+ * bejegyzés, ami nem fájlból jött), akkor az `id` — így minden ilyen entry EGYEDI marad, és a chunk-collapse
25
+ * nem nyúl hozzájuk (nincs mit összevonni).
26
+ */
27
+ static fileKey(hit) {
28
+ return hit.absolutePath || hit.sourceFilePath || `id:${hit.id}`;
29
+ }
30
+ /**
31
+ * Diverzifikálja a RENDEZETT találati listát: (a) fájlonként legfeljebb `maxChunksPerFile` chunk kerül előre,
32
+ * (b) minden kért tár, amelynek VAN találata, kap legalább `tableMinShare` helyet az első `windowSize`-ban.
33
+ * A `pinned` hitek sorrendje és tagsága garantáltan változatlan.
34
+ */
35
+ static diversify(set) {
36
+ const pinned = set.isPinned || (() => false);
37
+ if (set.hits.length <= 1) {
38
+ return set.hits;
39
+ }
40
+ const collapsed = this.collapseChunks(set.hits, set.maxChunksPerFile, pinned);
41
+ return this.enforceTableShare(collapsed, set.tableMinShare, set.windowSize, pinned);
42
+ }
43
+ /**
44
+ * (a) Chunk-collapse: fájlonként az első `max` chunk marad a helyén, a többi a lista végére csúszik. `max <= 0`
45
+ * → kikapcsolva (a hívó config-ból tilthatja).
46
+ */
47
+ static collapseChunks(hits, max, pinned) {
48
+ if (max <= 0) {
49
+ return hits;
50
+ }
51
+ const kept = [];
52
+ const demoted = [];
53
+ const perFile = new Map();
54
+ for (const hit of hits) {
55
+ // A kód-exact citáció (F3-1) MINDIG marad — a determinisztikus garancia erősebb a diverzitásnál.
56
+ if (pinned(hit)) {
57
+ kept.push(hit);
58
+ continue;
59
+ }
60
+ const key = this.fileKey(hit);
61
+ const seen = perFile.get(key) || 0;
62
+ if (seen < max) {
63
+ perFile.set(key, seen + 1);
64
+ kept.push(hit);
65
+ }
66
+ else {
67
+ demoted.push(hit);
68
+ }
69
+ }
70
+ return kept.concat(demoted);
71
+ }
72
+ /**
73
+ * (b) Tár-kvóta: ha egy kért tárnak van találata, de az első `windowSize`-ban kevesebb, mint `minShare`, akkor a
74
+ * legjobb helyezésű ilyen találatát ELŐRE hozzuk a window UTOLSÓ (nem-pinned) helyére — minimális bolygatással,
75
+ * a kiszorult elem eggyel hátrébb csúszik (nem esik ki a listából).
76
+ */
77
+ static enforceTableShare(hits, minShare, windowSize, pinned) {
78
+ if (minShare <= 0 || windowSize <= 0 || hits.length <= windowSize) {
79
+ return hits;
80
+ }
81
+ const result = hits.slice();
82
+ const maxPromotions = Math.max(1, Math.floor(windowSize * this.MAX_PROMOTION_RATIO));
83
+ let promotions = 0;
84
+ // A tárak sorrendje a LEGJOBB helyezésük szerint — a leginkább „majdnem befért" tár kap előbb kvótát.
85
+ for (const table of this.tablesByBestRank(result)) {
86
+ if (promotions >= maxPromotions) {
87
+ break;
88
+ }
89
+ const inWindow = result.slice(0, windowSize).filter((hit) => hit.table === table).length;
90
+ if (inWindow >= minShare) {
91
+ continue;
92
+ }
93
+ const donorIndex = result.findIndex((hit, index) => index >= windowSize && hit.table === table);
94
+ if (donorIndex < 0) {
95
+ continue;
96
+ }
97
+ // A window utolsó NEM-pinned helye a beszúrási pont; ha minden hely pinned, nem bolygatunk.
98
+ const slot = this.lastUnpinnedSlot(result, windowSize, pinned);
99
+ if (slot < 0) {
100
+ break;
101
+ }
102
+ const [promoted] = result.splice(donorIndex, 1);
103
+ result.splice(slot, 0, promoted);
104
+ promotions++;
105
+ }
106
+ return result;
107
+ }
108
+ /** A találati listában szereplő tárak, a LEGJOBB (legkisebb indexű) helyezésük szerint rendezve. */
109
+ static tablesByBestRank(hits) {
110
+ const bestRank = new Map();
111
+ hits.forEach((hit, index) => {
112
+ if (!bestRank.has(hit.table)) {
113
+ bestRank.set(hit.table, index);
114
+ }
115
+ });
116
+ return Array.from(bestRank.entries()).sort((a, b) => a[1] - b[1]).map((entry) => entry[0]);
117
+ }
118
+ /** A window utolsó nem-pinned indexe (ide szúrjuk a promótált hitet), vagy `-1` ha minden hely pinned. */
119
+ static lastUnpinnedSlot(hits, windowSize, pinned) {
120
+ for (let index = windowSize - 1; index >= 0; index--) {
121
+ if (!pinned(hits[index])) {
122
+ return index;
123
+ }
124
+ }
125
+ return -1;
126
+ }
127
+ }
128
+ exports.FAM_ResultDiversity_Util = FAM_ResultDiversity_Util;
@@ -14,6 +14,8 @@ const TAG_FREQUENCY_RATIO = 0.4;
14
14
  * feltételhez kötött (graceful: a hiányzó statisztikájú javaslat KIMARAD, nem hiba — dsgn-005 §kockázat).
15
15
  */
16
16
  class FAM_RetrievalSuggestions_Util {
17
+ /** A topK-javaslat abszolut plafonja — e folott a javaslat kontextus-bombava valik (2026-08-22 meres). */
18
+ static SUGGEST_TOPK_CAP = 25;
17
19
  /**
18
20
  * A teljes, rendezett javaslat-lista (dsgn-005 §4.4). A sorrend kötött (scope → tag → minScore →
19
21
  * topK → kind). Üres relevantSet → `[]`. A scope/tag/kind a relevantSet eloszlásából; a minScore a
@@ -147,8 +149,18 @@ class FAM_RetrievalSuggestions_Util {
147
149
  return undefined;
148
150
  }
149
151
  const totalRelevant = input.relevantSet.length;
150
- const suggested = Math.min(totalRelevant, input.maxPageSize);
151
- return `emeld a topK-t ${suggested}-re a teljes releváns halmazért (totalRelevant=${totalRelevant})`;
152
+ // MERSEKELT lepes, NEM „hozd be az egeszet" (2026-08-22, negy fuggetlen agent-meres): a javaslat korabban a
153
+ // teljes relevans halmazt ajanlotta (merve: `topK: 859`, `906`, `674`), ami (a) kontextus-bomba egy agent a
154
+ // tanacsot kovetve tobb tizezer tokent egetett el —, es (b) TELJESITHETETLEN: a `topK: 859` valojaban 77
155
+ // talalatot adott, mire a valasz a hivo `topK`-jat okolta a vagasert. Ezert: legfeljebb a jelenlegi ablak
156
+ // haromszorosa, `SUGGEST_TOPK_CAP`-re vagva, es a content-kikapcsolas tanacsaval egyutt.
157
+ const step = Math.min(totalRelevant, Math.max(input.effectiveTopK * 3, input.effectiveTopK + 1), FAM_RetrievalSuggestions_Util.SUGGEST_TOPK_CAP, input.maxPageSize);
158
+ if (step <= input.effectiveTopK) {
159
+ return undefined;
160
+ }
161
+ return `emeld a topK-t ${step}-re a kovetkezo adagert (a kuszob felett osszesen ${totalRelevant} elem van — `
162
+ + 'ezt NE probald egyben lekerni; ha tobb kell, lapozz tovabb, es tartsd `includeContent: false`-on, '
163
+ + 'amig ki nem valasztottad, melyik talalat kell tartalommal)';
152
164
  }
153
165
  /**
154
166
  * kindFilter-javaslat (dsgn-005 §4.4 #5): csak ha a relevantSet ≥2 különböző `kind`-ot tartalmaz
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FAM_Retrieval_ControlService = void 0;
3
+ exports.FAM_Retrieval_ControlService = exports.MAX_PAGE_SIZE = void 0;
4
4
  const fam_table_type_enum_1 = require("../../../_enums/fam-table.type-enum");
5
5
  const fam_reference_code_util_1 = require("../../../_collections/fam-reference-code.util");
6
6
  const config_control_service_1 = require("../../../_routes/server/config/config.control-service");
@@ -10,10 +10,11 @@ const fam_retrieval_candidate_data_service_1 = require("./fam-retrieval-candidat
10
10
  const fam_retrieval_suggestions_util_1 = require("./fam-retrieval-suggestions.util");
11
11
  const fam_memory_activation_util_1 = require("../_collections/fam-memory-activation.util");
12
12
  const fam_lexical_match_util_1 = require("../_collections/fam-lexical-match.util");
13
+ const fam_result_diversity_util_1 = require("../_collections/fam-result-diversity.util");
13
14
  const fam_memory_reactivation_control_service_1 = require("./fam-memory-reactivation.control-service");
14
15
  const fam_memory_cold_search_control_service_1 = require("./fam-memory-cold-search.control-service");
15
16
  /** A topK / lapozás abszolút plafonja (CCAP MAX_PAGE_SIZE=500; dsgn-005 §6). */
16
- const MAX_PAGE_SIZE = 500;
17
+ exports.MAX_PAGE_SIZE = 500;
17
18
  /**
18
19
  * `FAM_Retrieval_ControlService` (MP-5, dsgn-005) — a `read` **egylépcsős** retrieval-pipeline ENGINE-je.
19
20
  * Singleton. Egy hívás több táblát + több query-t kezel; minden query ÖNÁLLÓ, kötött-sorrendű
@@ -60,7 +61,10 @@ class FAM_Retrieval_ControlService {
60
61
  async read(request) {
61
62
  const includeContent = request.includeContent !== false;
62
63
  // Per-query párhuzamosítható pipeline-ok (egymástól függetlenek; dsgn-005 §2).
63
- const results = await Promise.all(request.queries.map((query) => this.runQuery(query, includeContent, request.globalScopeFilter)));
64
+ const results = await Promise.all(
65
+ // A QUERY sajat `includeContent`-je NYER a request-szintu felett (2026-08-14 lelet: eddig a query-szintu
66
+ // ertek nemán elnyelodott, ezert a „kompakt lista" szandeku hivas teljes tartalmat adott vissza).
67
+ request.queries.map((query) => this.runQuery(query, query.includeContent !== undefined ? query.includeContent !== false : includeContent, request.globalScopeFilter)));
64
68
  return { results: results };
65
69
  }
66
70
  /**
@@ -80,7 +84,9 @@ class FAM_Retrieval_ControlService {
80
84
  const queryVector = await this.embedQuery(query.query, primaryTable);
81
85
  // ②–④ per-tár: Mongo-prefilter → vektor-keresés a szűkített poolon → hit-row összefűzés.
82
86
  const allHits = [];
83
- for (const table of query.tables) {
87
+ // TÁR-DEDUP (2026-08-14 battery-lelet): a `tables:['rules','rules']` KÉTSZER futtatta ugyanazt a keresést
88
+ // (mérve: 1036ms vs 558ms) — azonos eredményért dupla vektor-munka. A dedup után a hívó hibája ingyenes.
89
+ for (const table of Array.from(new Set(query.tables))) {
84
90
  const tableHits = await this.searchTable({ table, query, queryVector, scopeExpand, config, includeContent });
85
91
  allHits.push(...tableHits);
86
92
  }
@@ -270,7 +276,7 @@ class FAM_Retrieval_ControlService {
270
276
  vectorHits = await embedding_1.FAM_VectorSearch_ControlService.getInstance().search({
271
277
  table: set.table,
272
278
  queryVector: set.queryVector,
273
- topK: MAX_PAGE_SIZE,
279
+ topK: exports.MAX_PAGE_SIZE,
274
280
  });
275
281
  if (!vectorHits.length) {
276
282
  return [];
@@ -306,7 +312,7 @@ class FAM_Retrieval_ControlService {
306
312
  table: set.table,
307
313
  queryVector: set.queryVector,
308
314
  candidateIds: candidateIds,
309
- topK: Math.min(candidateIds.length, MAX_PAGE_SIZE),
315
+ topK: Math.min(candidateIds.length, exports.MAX_PAGE_SIZE),
310
316
  });
311
317
  }
312
318
  // ⑤ weight-szorzó: finalScore = score × (weight ?? 1); weight ≤ 0 → kizárt (nem kerül a hits közé).
@@ -408,11 +414,13 @@ class FAM_Retrieval_ControlService {
408
414
  * → tiszta relevancia-rangsor; az identity-query-ken a summary a MAGAS cosine miatt így is felszínre kerül. Minden
409
415
  * NEM-scan-summary source érintetlen (×1). READ-time, a nyers `score` (relevancia) változatlan.
410
416
  */
411
- scanSummaryFactor(source, weight, scanSummaryWeight) {
412
- if (source?.type !== 'scan-summary' || weight <= scanSummaryWeight) {
417
+ scanSummaryFactor(source, effective, scanSummaryWeight) {
418
+ // FONTOS (2026-08-14): a cap a TEMPERÁLT (effektív) súlyra vonatkozik, nem a nyersre — különben a
419
+ // `effectiveWeight` és a cap KÉTSZER vonná le ugyanazt (mérve: w3 → 1.24 × 0.333 = 0.41 a kívánt 1.0 helyett).
420
+ if (source?.type !== 'scan-summary' || effective <= scanSummaryWeight) {
413
421
  return 1;
414
422
  }
415
- return scanSummaryWeight / weight;
423
+ return scanSummaryWeight / effective;
416
424
  }
417
425
  /**
418
426
  * Memory Activation Model decay-szorzó (dsgn-013 §3) — CSAK a `memory` táron, ha engedélyezett. A frissen/gyakran
@@ -453,7 +461,7 @@ class FAM_Retrieval_ControlService {
453
461
  // finalScore = score × weight × chunkF × sourceF × decayF × (1 + keywordWeight × lexicalScore).
454
462
  const chunkFactor = this.chunkTypeFactor(set.entry.chunkType, set.importChunkWeight);
455
463
  const sourceFactor = this.importSourceFactor(set.entry.source, set.importSourceWeight);
456
- const summaryFactor = this.scanSummaryFactor(set.entry.source, set.weight, set.config.scanSummaryWeight);
464
+ const summaryFactor = this.scanSummaryFactor(set.entry.source, FAM_Retrieval_ControlService.effectiveWeight(set.weight, set.config.weightInfluence), set.config.scanSummaryWeight);
457
465
  const decayFactor = this.memoryDecayFactor(set.entry, set.table, set.config, set.nowMs);
458
466
  const lexicalScore = set.config.keywordWeight > 0
459
467
  ? fam_lexical_match_util_1.FAM_LexicalMatch_Util.score(set.queryTerms, {
@@ -466,7 +474,7 @@ class FAM_Retrieval_ControlService {
466
474
  table: set.table,
467
475
  score: set.vectorHit.score,
468
476
  weight: set.weight,
469
- finalScore: set.vectorHit.score * set.weight * chunkFactor * sourceFactor * summaryFactor * decayFactor * keywordBoost,
477
+ finalScore: set.vectorHit.score * FAM_Retrieval_ControlService.effectiveWeight(set.weight, set.config.weightInfluence) * chunkFactor * sourceFactor * summaryFactor * decayFactor * keywordBoost,
470
478
  lexicalScore: lexicalScore,
471
479
  content: set.includeContent ? set.entry.content : undefined,
472
480
  contentHash: set.entry.contentHash,
@@ -485,6 +493,18 @@ class FAM_Retrieval_ControlService {
485
493
  lastModifiedMs: lastModified ? new Date(lastModified).getTime() : 0,
486
494
  };
487
495
  }
496
+ /**
497
+ * A statikus `weight` TEMPERÁLT (effektív) értéke (PURE, 2026-08-14 lelet). A `finalScore` eddig LINEÁRISAN
498
+ * szorzott a weight-tel, a koszinusz viszont csak ~0.3-0.8 között mozog — így egy `weight:5` tétel
499
+ * MATEMATIKAILAG VERHETETLEN volt. Mérve a `rules` táron: 2 db w5-os bejegyzés ült MINDEN szabály-keresés
500
+ * tetején (0.519 × 5 = 2.595, míg a HELYES találat 0.621 × 1 = 0.742) — a súly ELNYOMTA a relevanciát.
501
+ * Mostantól `1 + influence × (weight - 1)`: a weight DÖNTETLENT BONT a kurált tartalom felé, de nem ír felül
502
+ * egy tisztán jobb szemantikai egyezést. Az AZONOS súlyú tételek sorrendje VÁLTOZATLAN. `influence:1` = a régi
503
+ * lineáris viselkedés, `0` = a weight figyelmen kívül. A `weight <= 0` (KIZÁRÁS) érintetlen marad.
504
+ */
505
+ static effectiveWeight(weight, influence) {
506
+ return weight <= 0 ? weight : 1 + influence * (weight - 1);
507
+ }
488
508
  /**
489
509
  * A `rules` tár per-tár metaadatai a hit-en (`category`/`isActive`/`applicableScopes`) — FEAT-005/006 P1:
490
510
  * a zóna-alapú aktív-rule kezelés alapja, hogy a hívó/hook a keresési hit-ből is lássa, MI aktív + mely
@@ -592,7 +612,17 @@ class FAM_Retrieval_ControlService {
592
612
  // query-n `#codeExact = 0` → `effectiveTopK = topK` (változatlan viselkedés).
593
613
  const codeExactCount = set.relevantSet.filter((hit) => this.codeExactRank(hit)).length;
594
614
  const effectiveTopK = Math.max(set.config.topK, codeExactCount);
595
- const returnedHits = set.relevantSet.slice(0, effectiveTopK);
615
+ // Diverzitas (dogfood 2026-08-22): a top-K slotok 44%-a ugyanannak a fajlnak masik chunkja volt, es tobb-taras
616
+ // query-ben a `rules` 0/3 aranyban tunt el a `documents` mogott. A demote SOHA nem dob el talalatot, ezert a
617
+ // `confidentSet`/`totalRelevant` statisztika (lentebb, a teljes halmazon) valtozatlan marad.
618
+ const diversified = fam_result_diversity_util_1.FAM_ResultDiversity_Util.diversify({
619
+ hits: set.relevantSet,
620
+ maxChunksPerFile: set.config.maxChunksPerFile,
621
+ tableMinShare: set.query.tables.length > 1 ? set.config.tableMinShare : 0,
622
+ windowSize: effectiveTopK,
623
+ isPinned: (hit) => !!this.codeExactRank(hit),
624
+ });
625
+ const returnedHits = diversified.slice(0, effectiveTopK);
596
626
  // Magabiztosan releváns halmaz: a nyers cosine `score` (NEM finalScore — a weight ranking-boost, nem
597
627
  // relevancia) a `relevanceFloor` felett. A relevantSet rendezett, ezért a confidentSet is.
598
628
  const confidentSet = set.relevantSet.filter((hit) => hit.score >= set.config.relevanceFloor);
@@ -646,7 +676,7 @@ class FAM_Retrieval_ControlService {
646
676
  query: set.query,
647
677
  effectiveTopK: set.effectiveTopK,
648
678
  truncated: set.truncated,
649
- maxPageSize: MAX_PAGE_SIZE,
679
+ maxPageSize: exports.MAX_PAGE_SIZE,
650
680
  });
651
681
  }
652
682
  // =========================================================================
@@ -669,13 +699,16 @@ class FAM_Retrieval_ControlService {
669
699
  ? await this.resolveNumber(config, 'read.deepTopK', ctx, 25)
670
700
  : await this.resolveNumber(config, 'read.topK', ctx, 5));
671
701
  const minScore = query.minScore ?? await this.resolveNumber(config, 'read.minScore', ctx, 0);
672
- const relevanceFloor = await this.resolveNumber(config, 'read.relevanceFloor', ctx, 0.5);
702
+ const relevanceFloor = await this.resolveNumber(config, 'read.relevanceFloor', ctx, 0.60);
673
703
  const importChunkWeight = await this.resolveNumber(config, 'read.importChunkWeight', ctx, 0.4);
674
704
  const importSourceWeight = await this.resolveNumber(config, 'read.importSourceWeight', ctx, 0.85);
675
- const scanSummaryWeight = await this.resolveNumber(config, 'read.scanSummaryWeight', ctx, 1.0);
705
+ const scanSummaryWeight = await this.resolveNumber(config, 'read.scanSummaryWeight', ctx, 0.8);
676
706
  const keywordWeight = await this.resolveNumber(config, 'read.keywordWeight', ctx, 0.6);
707
+ const weightInfluence = await this.resolveNumber(config, 'read.weightInfluence', ctx, 0.06);
677
708
  const denseResultRatio = await this.resolveNumber(config, 'read.denseResultRatio', ctx, 2.0);
678
709
  const denseResultAbsMin = await this.resolveNumber(config, 'read.denseResultAbsMin', ctx, 25);
710
+ const maxChunksPerFile = await this.resolveNumber(config, 'read.maxChunksPerFile', ctx, 2);
711
+ const tableMinShare = await this.resolveNumber(config, 'read.tableMinShare', ctx, 1);
679
712
  const scoreOrderValue = (await config.resolve('read.scoreOrder', ctx)).value;
680
713
  const scoreOrder = typeof scoreOrderValue === 'string' ? scoreOrderValue : 'weighted';
681
714
  // Memory Activation Model (dsgn-013) — a `memory.*` namespace; a decay CSAK a memory táron alkalmazódik (toHit).
@@ -692,15 +725,18 @@ class FAM_Retrieval_ControlService {
692
725
  const memoryColdSearchMaxCandidates = await this.resolveNumber(config, 'memory.coldSearchMaxCandidates', ctx, 5000);
693
726
  const memorySupersededPenalty = await this.resolveNumber(config, 'memory.supersededPenalty', ctx, 0.25);
694
727
  return {
695
- topK: Math.min(topKDefault, MAX_PAGE_SIZE),
728
+ topK: Math.min(topKDefault, exports.MAX_PAGE_SIZE),
696
729
  minScore: minScore,
697
730
  relevanceFloor: relevanceFloor,
698
731
  importChunkWeight: importChunkWeight,
699
732
  importSourceWeight: importSourceWeight,
700
733
  scanSummaryWeight: scanSummaryWeight,
701
734
  keywordWeight: keywordWeight,
735
+ weightInfluence: weightInfluence,
702
736
  denseResultRatio: denseResultRatio,
703
737
  denseResultAbsMin: denseResultAbsMin,
738
+ maxChunksPerFile: maxChunksPerFile,
739
+ tableMinShare: tableMinShare,
704
740
  scoreOrder: scoreOrder,
705
741
  memoryActivationEnabled: memoryActivationEnabled,
706
742
  memoryHalfLifeDays: memoryHalfLifeDays,
@@ -10,10 +10,11 @@
10
10
  * opcionális relevance-filter = BACKLOG (NEM épül).
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.FAM_RetrievalSuggestions_Util = exports.FAM_RetrievalCandidate_DataService = exports.FAM_RuleScopeContext_Util = exports.FAM_RulePropagation_Util = exports.FAM_RuleInjection_Util = exports.FAM_RuleInjection_ControlService = exports.FAM_GlobalRule_ControlService = exports.FAM_Retrieval_ControlService = void 0;
13
+ exports.FAM_RetrievalSuggestions_Util = exports.FAM_RetrievalCandidate_DataService = exports.FAM_RuleScopeContext_Util = exports.FAM_RulePropagation_Util = exports.FAM_RuleInjection_Util = exports.FAM_RuleInjection_ControlService = exports.FAM_GlobalRule_ControlService = exports.MAX_PAGE_SIZE = exports.FAM_Retrieval_ControlService = void 0;
14
14
  // SP-5.1/5.2/5.3 — a read-pipeline orchestrátor
15
15
  var fam_retrieval_control_service_1 = require("./_services/fam-retrieval.control-service");
16
16
  Object.defineProperty(exports, "FAM_Retrieval_ControlService", { enumerable: true, get: function () { return fam_retrieval_control_service_1.FAM_Retrieval_ControlService; } });
17
+ Object.defineProperty(exports, "MAX_PAGE_SIZE", { enumerable: true, get: function () { return fam_retrieval_control_service_1.MAX_PAGE_SIZE; } });
17
18
  var fam_global_rule_control_service_1 = require("./_services/fam-global-rule.control-service");
18
19
  Object.defineProperty(exports, "FAM_GlobalRule_ControlService", { enumerable: true, get: function () { return fam_global_rule_control_service_1.FAM_GlobalRule_ControlService; } });
19
20
  // Rules-Management MP-2 — always-apply rule-injektálás (SessionStart inject + read-reflex) szerver-oldali assembly
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@futdevpro/fdp-agent-memory",
3
- "version": "1.1.137",
3
+ "version": "1.1.153",
4
4
  "description": "Local-first, vector-backed multi-table agent memory exposed as an MCP server (read/write/capabilities). Public, FDP-Templates-free, no auth.",
5
5
  "private": false,
6
6
  "publishConfig": {