@futdevpro/fdp-agent-memory 1.1.21 → 1.1.30

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 (21) hide show
  1. package/build/package.json +2 -2
  2. package/build/src/_cli/_commands/scan-projects.command.js +1 -1
  3. package/build/src/_collections/config-catalog.const.js +4 -27
  4. package/build/src/_collections/title.const.js +26 -26
  5. package/build/src/_modules/capture/_services/fam-auto-capture.control-service.js +1 -1
  6. package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +1 -1
  7. package/build/src/_modules/embedding/_services/fam-entry.data-service.js +23 -1
  8. package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +5 -1
  9. package/build/src/_modules/mcp/_collections/fam-core-tools.const.js +1 -1
  10. package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +113 -33
  11. package/build/src/_modules/mcp/_services/fam-read-tool.service.js +1 -1
  12. package/build/src/_modules/migration/_services/fam-claude-mem-import.control-service.js +1 -1
  13. package/build/src/_modules/scope-reference/_collections/fam-scope-maintenance.util.js +58 -0
  14. package/build/src/_modules/scope-reference/_services/fam-scope-maintenance.control-service.js +190 -0
  15. package/build/src/_modules/scope-reference/_services/fam-scope-resolver.control-service.js +2 -2
  16. package/build/src/_modules/scope-reference/index.js +4 -1
  17. package/build/src/_routes/server/api/api.controller.js +54 -23
  18. package/build/src/_routes/server/config/config.control-service.js +2 -2
  19. package/build/src/app.server.js +26 -27
  20. package/package.json +2 -2
  21. package/build/src/_collections/fam-heap-guard.control-service.js +0 -176
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@futdevpro/fdp-agent-memory",
3
- "version": "1.1.21",
3
+ "version": "1.1.30",
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": {
@@ -55,7 +55,7 @@
55
55
  "license": "FDP",
56
56
  "dependencies": {
57
57
  "@futdevpro/fsm-dynamo": "^1.16.7",
58
- "@futdevpro/nts-dynamo": "^1.15.72",
58
+ "@futdevpro/nts-dynamo": "^1.15.74",
59
59
  "@modelcontextprotocol/sdk": "^1.12.0",
60
60
  "axios": "~1.8.1",
61
61
  "better-sqlite3": "^12.4.1",
@@ -22,7 +22,7 @@ class FAM_ScanProjects_Util {
22
22
  .description('Több projekt SOROS scan-je (auto-felderítés --root-tal vagy explicit --config-gal; nincs '
23
23
  + 'konkurrens thrash). ⚠️ MINDEN projekt-gyökérbe előbb `.fdpfamignore` + `--dry-run` (lásd lent).')
24
24
  .option('--root <dir>', 'projekt-gyökerek auto-felderítése e mappa alatt (package.json/.git markerek)')
25
- .option('--config <file.json>', 'explicit projekt-lista: [{ path, project, org?, table? }]')
25
+ .option('--config <file.json>', 'explicit projekt-lista JSON — elemenként `path` + `project` (+ opcionális `org`, `table`)')
26
26
  .option('--depth <n>', 'auto-felderítés max mélysége (default 3)', '3')
27
27
  .option('--org <name>', 'default organization az auto-felderítéshez', 'FutDevPro')
28
28
  .option('--table <documents|codebase>', 'default cél-tár', 'codebase')
@@ -87,7 +87,7 @@ exports.CONFIG_CATALOG = {
87
87
  'read.importChunkWeight': {
88
88
  type: 'number', default: 0.4, min: 0.0, max: 1.0, levels: ALL_LEVELS,
89
89
  description: 'Az import-only chunkok (`chunkType:"imports"`) read-idejű rangsor-szorzója (FAM-REV-060). '
90
- + 'A szimbólum-név query-knél az import-sorok (`import { X } from "./y"`) magas cosine-t kapnak, de '
90
+ + 'A szimbólum-név query-knél az import-sorok (`import { X: X } from "./y"`) magas cosine-t kapnak, de '
91
91
  + 'alacsony az ÉRTÉKük (csak import, nem definíció) → e szorzóval a `finalScore = score × weight × '
92
92
  + 'importChunkWeight` a definíció-chunkok ALÁ rangsorolja őket. READ-time (nincs re-embed); a nyers '
93
93
  + 'cosine score (relevancia-jelzések) NEM változik, csak a ranking. 1 = nincs csökkentés.',
@@ -269,32 +269,9 @@ exports.CONFIG_CATALOG = {
269
269
  type: 'number', default: 20, min: 1, max: 200, integer: true, levels: GLOBAL_TABLE,
270
270
  description: 'Párhuzamos ingest flow-limit (CCAP DEFAULT_INGEST_FLOW_LIMIT=20, cap 200).',
271
271
  },
272
- // --- 4.8. Memória / heap-guard (OOM-hibakezelés, fogyasztó: FAM_HeapGuard_ControlService) ---
273
- 'memory.guardEnabled': {
274
- type: 'boolean', default: true, levels: GLOBAL_ONLY,
275
- description: 'A heap-guard (OOM-hibakezelés) be/ki. `true` (default): watchdog (heap-arány + GC-hányad) + a '
276
- + 'nehéz write-ek (scan/import/re-embed) load-shed-je critical nyomásnál + graceful-exit a near-limit '
277
- + 'zónában (a kriptikus exit-134 helyett). `false`: csak a crash-handlerek (uncaught/unhandled) aktívak.',
278
- },
279
- 'memory.warnRatio': {
280
- type: 'number', default: 0.70, min: 0.1, max: 1.0, levels: GLOBAL_ONLY,
281
- description: 'Heap-arány (used/limit), ami fölött a guard `warn` szintet jelez (log; még nincs load-shed).',
282
- },
283
- 'memory.shedRatio': {
284
- type: 'number', default: 0.82, min: 0.1, max: 1.0, levels: GLOBAL_ONLY,
285
- description: 'Heap-arány, ami fölött `critical` → a nehéz write-ek (scan/import/re-embed) 503-mal elutasítva '
286
- + '(a könnyű read/create/update fut tovább). A GC-hányad ≥60% is critical-t vált (mark-compact thrash-jel).',
287
- },
288
- 'memory.exitRatio': {
289
- type: 'number', default: 0.92, min: 0.1, max: 1.0, levels: GLOBAL_ONLY,
290
- description: 'Heap-arány, ami fölött tartósan (GC-vel sem csökken) a guard TISZTÁN kilép (exit 137) → a `fam '
291
- + 'serve` ps1 prediktíven újraindít. A kriptikus „Ineffective mark-compacts" exit-134 elkerülése. shed < exit ajánlott.',
292
- },
293
- 'memory.pollMs': {
294
- type: 'number', default: 3000, min: 500, max: 60000, integer: true, levels: GLOBAL_ONLY,
295
- description: 'A heap-watchdog mintavételi időköze (ms) + a GC-hányad gördülő-ablaka. Kisebb = gyorsabb '
296
- + 'reakció a hirtelen növekedésre, több CPU. 3000 = jó alapérték.',
297
- },
272
+ // (Az OOM-hibakezelés/heap-guard configja a BEDROCK `DyNTS_global_settings.memoryGuard`-ban van — NEM FAM-config:
273
+ // a bedrock `DyNTS_MemoryGuard`-ot a base App auto-installálja, a FAM a thresholdokat az `overrideDynamoNTSGlobalSettings`-ben
274
+ // állítja. Ezért nincs FAM `memory.*` config-kulcs.)
298
275
  'patterns.defaultWeight': {
299
276
  type: 'number', default: 5, min: 0, levels: [fam_config_level_type_enum_1.FAM_ConfigLevel.table], tableOnly: fam_table_type_enum_1.FAM_Table.codingPatterns,
300
277
  description: 'A coding_patterns tár default retrieval-súlya (CCAP weight 5; csak coding_patterns).',
@@ -2,30 +2,30 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FAM_title = void 0;
4
4
  exports.FAM_title = `
5
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⠤⠤⠤⠤⣄⠀⠀⠀⠀⠀⠀⢀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
6
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣶⡊⠉⠉⣉⣱⡷⠶⢢⣠⢴⣶⡝⠒⠉⢉⣭⡽⠟⢉⣀⡀⠹⢭⠒⢤⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
7
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠔⢚⣩⡽⠿⠊⢉⣉⡂⣀⣩⠭⢴⠟⠋⠉⠉⠉⠛⠳⢦⣬⣤⡴⠞⠛⠁⠛⠳⣾⣧⠀⠟⠀⠉⠲⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
8
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⢚⠁⠀⠰⠋⢡⠄⠀⠞⣫⢟⡥⠒⠉⠹⣿⡀⠀⠀⢦⡀⠀⠀⠀⠈⠻⡧⡀⠀⠀⠀⠀⠈⠻⣗⡶⠶⠶⢤⡀⠱⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀
9
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢮⣤⡾⠀⠀⣠⡴⠋⠀⡠⣚⠥⠒⢛⡲⠄⠀⠈⢻⡆⠀⠀⠻⣦⣀⠀⠀⠀⣿⠻⣦⣀⣴⠶⠂⠀⠘⣷⡄⠀⠀⢀⣴⡿⠈⠢⡀⠀⠀⠀⠀⠀⠀
10
- ⠀⠀⠀⠀⠀⠀⠀⡠⠖⣉⣁⡀⠀⢀⣾⠋⠴⢿⣽⠋⠀⠞⢉⣉⣽⣳⣄⣀⠀⠋⠀⠀⠀⠈⠙⣷⡄⠀⠁⠀⠙⢤⣯⡀⠀⠀⠀⣼⡇⠀⡾⠋⠁⠀⠳⣄⠘⢆⠀⠀⠀⠀⠀
11
- ⠀⠀⠀⠀⠀⡰⠋⠰⠛⢻⡞⢉⣠⣼⡇⢀⣴⠟⠛⠒⣴⠟⠋⠉⠀⠀⢀⣀⣀⡀⠀⠀⠀⠀⠀⣸⡇⠀⠀⠳⣄⠀⠉⢿⣄⠀⢰⣿⣧⡀⠀⣴⠶⠶⣦⡼⢧⠈⢣⠀⠀⠀⠀
12
- ⠀⠀⠀⢀⡞⣡⣶⡄⠀⡟⣳⠿⠋⠙⡍⡽⠁⣀⣤⣤⣿⡄⠀⠀⠀⠀⡿⡉⣀⣀⣤⣤⣤⣴⠾⠥⠽⣦⣄⠀⠉⠻⢶⡼⢻⠀⠈⠇⠘⡷⡄⠘⠂⠀⢀⡍⠻⣷⣄⡇⠀⠀⠀
13
- ⠀⠀⠀⠘⣺⠏⢸⢃⣼⠟⢁⡤⠀⣠⢟⡷⠟⢋⣉⣤⡿⠇⠀⠀⠀⢰⣣⠞⠋⠉⠉⠁⡀⠀⠀⠀⠀⠀⠙⢷⣄⠀⠀⢹⣾⠀⠀⠀⠀⢸⡇⠀⣀⡀⣾⠀⠀⠈⢻⡁⢦⠀⠀
14
- ⠀⠀⣠⢚⣵⣄⠈⣼⡇⠀⢸⠧⢞⡵⠋⠠⠚⠉⠉⠀⠀⢀⡇⠀⣰⣟⣁⣀⠀⠀⠀⠀⠉⠒⠶⣤⣤⣀⠀⠀⠙⠀⠀⢸⡇⢰⣟⠛⢶⡋⣇⠀⠉⠻⡟⡄⠀⢀⢀⣿⠀⢧⠀
15
- ⠀⣰⠃⢸⠁⣿⠀⠸⣧⠀⢸⢣⠋⠀⣠⣤⠶⢶⢒⣤⣔⣻⠣⢼⠟⠁⠀⠙⢷⡄⠀⠀⢀⠀⠀⠀⠈⠓⢟⢦⠀⠀⠀⢸⡇⠈⠻⣦⡀⠈⠻⣷⣄⠀⠘⣿⠀⠸⣿⣇⣀⢸⠀
16
- ⠀⡇⠀⠀⣼⠇⠀⢀⣿⠀⣇⣇⣴⠟⠋⢠⣾⠟⠉⠀⠀⠈⠳⣼⠀⠀⠀⠀⠀⠳⠀⠀⠈⢳⣄⠀⠀⠀⢸⣼⠀⠀⠀⠈⡟⢆⠀⠈⢻⡀⠀⠈⢻⣆⠀⣻⠃⠀⠀⢹⡟⠻⡀
17
- ⠀⢧⡆⣼⠏⠀⣾⠟⠁⢰⠃⡵⠃⢀⣴⡿⠁⠀⡀⠀⠀⠀⠀⠹⣧⡀⠰⣦⡀⠀⠀⠀⠀⠀⣻⢦⣀⣠⡾⣇⠀⠀⢀⣰⠟⠙⢷⣄⠀⠀⠀⠀⠀⣿⠀⠉⢠⠄⠀⣼⡇⠀⢧
18
- ⢀⠞⢡⡟⠀⠀⣿⠀⢀⡏⡼⠁⣴⠟⠁⠀⠀⠀⣿⠀⣀⣀⢀⣴⠘⣷⡀⠈⢻⣦⣀⠀⢀⣾⠟⠉⠀⠀⠉⠻⣷⣄⠀⠀⠀⠀⠀⠙⢷⡄⠀⠀⠀⠉⠀⣠⡟⠀⣼⣟⠀⠀⢸
19
- ⢸⠀⠘⣧⠀⡴⠛⠳⢸⢰⠁⢰⠏⠀⠀⠀⢀⣼⡯⠟⠋⠙⠻⣷⡀⠘⠀⠀⠀⠈⠉⠻⣿⠁⠀⠀⢰⡟⠉⠀⠈⢻⣦⠀⠀⠀⣄⠀⠀⡗⠀⢸⡇⣠⣾⠟⢀⣾⠋⠹⣷⢀⡇
20
- ⠈⢆⠀⠹⢷⣤⣀⣠⠎⡇⠀⠸⠀⠀⢀⣴⠟⠉⠀⠀⠀⢄⠀⠹⣧⡀⠀⠀⣀⡀⠀⠀⣿⠀⠀⠀⠘⣿⡄⠀⠀⠀⢹⣦⡀⠀⢿⣄⠀⢀⣠⡿⠽⣯⡁⠀⠸⠃⠀⠀⡏⠉⠀
21
- ⠀⢠⢷⣄⠀⠈⣉⣉⢢⢳⡀⠀⠀⠀⣾⡏⠀⠀⠠⣀⡤⢿⠀⠀⠙⠷⠶⠛⠉⠈⠀⣰⠟⠀⠀⠀⠀⠘⣷⡀⠀⠠⠛⠉⠉⠀⢈⣯⠗⠛⠁⠀⠀⠈⠃⠀⢀⣴⠇⢠⠇⠀⠀
22
- ⠀⢸⡀⠻⣧⠈⠉⠹⣏⢀⣑⠤⣀⣀⠼⠳⣄⠀⠀⠀⠙⠺⠖⣦⣤⠤⣀⡀⠀⠀⠘⠁⠀⠀⠀⠀⠀⠀⢸⢧⡀⠀⠀⢀⣀⢴⣿⣅⡀⠠⠶⢿⢦⣀⣠⣴⠟⠃⡠⠋⠀⠀⠀
23
- ⠀⠀⠳⡀⠘⠃⠀⡤⠸⣼⠀⠉⠛⠋⠉⠉⠙⠻⣦⣄⠀⠀⠀⠀⠈⠉⠙⠻⣦⠀⠀⠀⠀⡀⠀⠀⠀⢀⣾⠖⠚⠛⠛⠛⠋⠁⠀⠙⣷⠀⠀⣸⡴⠛⠉⢁⡤⠊⠁⠀⠀⠀⠀
24
- ⠀⠀⠀⠘⢦⡀⠸⣧⠀⢻⢇⠀⠳⡤⣤⠆⠀⠀⠈⢻⡇⠀⠀⠀⢰⡄⠀⠀⣿⡇⢀⡾⠛⠛⠻⡝⣲⠟⠋⠀⢀⡄⠀⠀⠀⣀⡄⠀⠋⢀⡴⣻⡄⣤⡶⡍⠀⠀⠀⠀⠀⠀⠀
25
- ⠀⠀⠀⠀⠀⠈⠙⠁⠉⠉⠈⠣⡀⠹⣇⠀⠀⠀⠀⠘⠀⠀⠀⢀⡾⢳⡶⠾⠋⠀⠈⠃⠀⠀⣠⠟⢄⣀⣠⡴⠋⠀⠀⠀⣼⢻⣤⣴⠶⠟⠋⣡⡷⣏⢿⡧⠀⠀⠀⠀⠀⠀⠀
26
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠓⢫⣲⣤⣀⣀⣀⣀⣤⣶⣻⠋⠛⠷⣦⣤⣤⣄⡤⢤⣺⠕⠋⠉⠉⠁⠀⠀⣀⣤⣾⠏⢩⠀⠀⢀⣤⣾⠛⣧⢻⣼⠀⠀⠀⠀⠀⠀⠀⠀
27
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠓⠮⢭⣉⣉⡩⠥⠚⠈⢇⠀⢠⡄⠀⠉⠉⠙⣿⠀⢠⠶⠖⢫⣩⠟⠛⠛⠉⠀⣠⣿⣦⠶⠿⣭⣸⣇⡿⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀
28
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣌⡿⣄⠀⠒⠚⠋⠀⠀⠀⣠⡾⠃⠀⢀⣀⠴⠚⠉⠣⢍⣛⣶⡶⠝⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
29
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠒⠂⠀⠒⠒⠉⠀⠉⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
30
-
5
+ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⠤⠤⠤⠤⣄⠀⠀⠀⠀⠀⠀⢀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
6
+ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣶⡊⠉⠉⣉⣱⡷⠶⢢⣠⢴⣶⡝⠒⠉⢉⣭⡽⠟⢉⣀⡀⠹⢭⠒⢤⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
7
+ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠔⢚⣩⡽⠿⠊⢉⣉⡂⣀⣩⠭⢴⠟⠋⠉⠉⠉⠛⠳⢦⣬⣤⡴⠞⠛⠁⠛⠳⣾⣧⠀⠟⠀⠉⠲⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
8
+ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⢚⠁⠀⠰⠋⢡⠄⠀⠞⣫⢟⡥⠒⠉⠹⣿⡀⠀⠀⢦⡀⠀⠀⠀⠈⠻⡧⡀⠀⠀⠀⠀⠈⠻⣗⡶⠶⠶⢤⡀⠱⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀
9
+ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢮⣤⡾⠀⠀⣠⡴⠋⠀⡠⣚⠥⠒⢛⡲⠄⠀⠈⢻⡆⠀⠀⠻⣦⣀⠀⠀⠀⣿⠻⣦⣀⣴⠶⠂⠀⠘⣷⡄⠀⠀⢀⣴⡿⠈⠢⡀⠀⠀⠀⠀⠀⠀
10
+ ⠀⠀⠀⠀⠀⠀⠀⡠⠖⣉⣁⡀⠀⢀⣾⠋⠴⢿⣽⠋⠀⠞⢉⣉⣽⣳⣄⣀⠀⠋⠀⠀⠀⠈⠙⣷⡄⠀⠁⠀⠙⢤⣯⡀⠀⠀⠀⣼⡇⠀⡾⠋⠁⠀⠳⣄⠘⢆⠀⠀⠀⠀⠀
11
+ ⠀⠀⠀⠀⠀⡰⠋⠰⠛⢻⡞⢉⣠⣼⡇⢀⣴⠟⠛⠒⣴⠟⠋⠉⠀⠀⢀⣀⣀⡀⠀⠀⠀⠀⠀⣸⡇⠀⠀⠳⣄⠀⠉⢿⣄⠀⢰⣿⣧⡀⠀⣴⠶⠶⣦⡼⢧⠈⢣⠀⠀⠀⠀
12
+ ⠀⠀⠀⢀⡞⣡⣶⡄⠀⡟⣳⠿⠋⠙⡍⡽⠁⣀⣤⣤⣿⡄⠀⠀⠀⠀⡿⡉⣀⣀⣤⣤⣤⣴⠾⠥⠽⣦⣄⠀⠉⠻⢶⡼⢻⠀⠈⠇⠘⡷⡄⠘⠂⠀⢀⡍⠻⣷⣄⡇⠀⠀⠀
13
+ ⠀⠀⠀⠘⣺⠏⢸⢃⣼⠟⢁⡤⠀⣠⢟⡷⠟⢋⣉⣤⡿⠇⠀⠀⠀⢰⣣⠞⠋⠉⠉⠁⡀⠀⠀⠀⠀⠀⠙⢷⣄⠀⠀⢹⣾⠀⠀⠀⠀⢸⡇⠀⣀⡀⣾⠀⠀⠈⢻⡁⢦⠀⠀
14
+ ⠀⠀⣠⢚⣵⣄⠈⣼⡇⠀⢸⠧⢞⡵⠋⠠⠚⠉⠉⠀⠀⢀⡇⠀⣰⣟⣁⣀⠀⠀⠀⠀⠉⠒⠶⣤⣤⣀⠀⠀⠙⠀⠀⢸⡇⢰⣟⠛⢶⡋⣇⠀⠉⠻⡟⡄⠀⢀⢀⣿⠀⢧⠀
15
+ ⠀⣰⠃⢸⠁⣿⠀⠸⣧⠀⢸⢣⠋⠀⣠⣤⠶⢶⢒⣤⣔⣻⠣⢼⠟⠁⠀⠙⢷⡄⠀⠀⢀⠀⠀⠀⠈⠓⢟⢦⠀⠀⠀⢸⡇⠈⠻⣦⡀⠈⠻⣷⣄⠀⠘⣿⠀⠸⣿⣇⣀⢸⠀
16
+ ⠀⡇⠀⠀⣼⠇⠀⢀⣿⠀⣇⣇⣴⠟⠋⢠⣾⠟⠉⠀⠀⠈⠳⣼⠀⠀⠀⠀⠀⠳⠀⠀⠈⢳⣄⠀⠀⠀⢸⣼⠀⠀⠀⠈⡟⢆⠀⠈⢻⡀⠀⠈⢻⣆⠀⣻⠃⠀⠀⢹⡟⠻⡀
17
+ ⠀⢧⡆⣼⠏⠀⣾⠟⠁⢰⠃⡵⠃⢀⣴⡿⠁⠀⡀⠀⠀⠀⠀⠹⣧⡀⠰⣦⡀⠀⠀⠀⠀⠀⣻⢦⣀⣠⡾⣇⠀⠀⢀⣰⠟⠙⢷⣄⠀⠀⠀⠀⠀⣿⠀⠉⢠⠄⠀⣼⡇⠀⢧
18
+ ⢀⠞⢡⡟⠀⠀⣿⠀⢀⡏⡼⠁⣴⠟⠁⠀⠀⠀⣿⠀⣀⣀⢀⣴⠘⣷⡀⠈⢻⣦⣀⠀⢀⣾⠟⠉⠀⠀⠉⠻⣷⣄⠀⠀⠀⠀⠀⠙⢷⡄⠀⠀⠀⠉⠀⣠⡟⠀⣼⣟⠀⠀⢸
19
+ ⢸⠀⠘⣧⠀⡴⠛⠳⢸⢰⠁⢰⠏⠀⠀⠀⢀⣼⡯⠟⠋⠙⠻⣷⡀⠘⠀⠀⠀⠈⠉⠻⣿⠁⠀⠀⢰⡟⠉⠀⠈⢻⣦⠀⠀⠀⣄⠀⠀⡗⠀⢸⡇⣠⣾⠟⢀⣾⠋⠹⣷⢀⡇
20
+ ⠈⢆⠀⠹⢷⣤⣀⣠⠎⡇⠀⠸⠀⠀⢀⣴⠟⠉⠀⠀⠀⢄⠀⠹⣧⡀⠀⠀⣀⡀⠀⠀⣿⠀⠀⠀⠘⣿⡄⠀⠀⠀⢹⣦⡀⠀⢿⣄⠀⢀⣠⡿⠽⣯⡁⠀⠸⠃⠀⠀⡏⠉⠀
21
+ ⠀⢠⢷⣄⠀⠈⣉⣉⢢⢳⡀⠀⠀⠀⣾⡏⠀⠀⠠⣀⡤⢿⠀⠀⠙⠷⠶⠛⠉⠈⠀⣰⠟⠀⠀⠀⠀⠘⣷⡀⠀⠠⠛⠉⠉⠀⢈⣯⠗⠛⠁⠀⠀⠈⠃⠀⢀⣴⠇⢠⠇⠀⠀
22
+ ⠀⢸⡀⠻⣧⠈⠉⠹⣏⢀⣑⠤⣀⣀⠼⠳⣄⠀⠀⠀⠙⠺⠖⣦⣤⠤⣀⡀⠀⠀⠘⠁⠀⠀⠀⠀⠀⠀⢸⢧⡀⠀⠀⢀⣀⢴⣿⣅⡀⠠⠶⢿⢦⣀⣠⣴⠟⠃⡠⠋⠀⠀⠀
23
+ ⠀⠀⠳⡀⠘⠃⠀⡤⠸⣼⠀⠉⠛⠋⠉⠉⠙⠻⣦⣄⠀⠀⠀⠀⠈⠉⠙⠻⣦⠀⠀⠀⠀⡀⠀⠀⠀⢀⣾⠖⠚⠛⠛⠛⠋⠁⠀⠙⣷⠀⠀⣸⡴⠛⠉⢁⡤⠊⠁⠀⠀⠀⠀
24
+ ⠀⠀⠀⠘⢦⡀⠸⣧⠀⢻⢇⠀⠳⡤⣤⠆⠀⠀⠈⢻⡇⠀⠀⠀⢰⡄⠀⠀⣿⡇⢀⡾⠛⠛⠻⡝⣲⠟⠋⠀⢀⡄⠀⠀⠀⣀⡄⠀⠋⢀⡴⣻⡄⣤⡶⡍⠀⠀⠀⠀⠀⠀⠀
25
+ ⠀⠀⠀⠀⠀⠈⠙⠁⠉⠉⠈⠣⡀⠹⣇⠀⠀⠀⠀⠘⠀⠀⠀⢀⡾⢳⡶⠾⠋⠀⠈⠃⠀⠀⣠⠟⢄⣀⣠⡴⠋⠀⠀⠀⣼⢻⣤⣴⠶⠟⠋⣡⡷⣏⢿⡧⠀⠀⠀⠀⠀⠀⠀
26
+ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠓⢫⣲⣤⣀⣀⣀⣀⣤⣶⣻⠋⠛⠷⣦⣤⣤⣄⡤⢤⣺⠕⠋⠉⠉⠁⠀⠀⣀⣤⣾⠏⢩⠀⠀⢀⣤⣾⠛⣧⢻⣼⠀⠀⠀⠀⠀⠀⠀⠀
27
+ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠓⠮⢭⣉⣉⡩⠥⠚⠈⢇⠀⢠⡄⠀⠉⠉⠙⣿⠀⢠⠶⠖⢫⣩⠟⠛⠛⠉⠀⣠⣿⣦⠶⠿⣭⣸⣇⡿⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀
28
+ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣌⡿⣄⠀⠒⠚⠋⠀⠀⠀⣠⡾⠃⠀⢀⣀⠴⠚⠉⠣⢍⣛⣶⡶⠝⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
29
+ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠒⠂⠀⠒⠒⠉⠀⠉⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
30
+
31
31
  FDP Agent Memory - By Future Development Program Kft.`;
@@ -48,7 +48,7 @@ class FAM_AutoCapture_ControlService {
48
48
  const contentHash = ingest_1.FAM_ContentHash_Util.hash(content);
49
49
  // Dedup: van-e MÁR aktív memory-bejegyzés azonos content-hash-sel? → idempotens skip (nincs duplikátum).
50
50
  const existing = await this.findByHash(contentHash);
51
- if (existing && existing._id) {
51
+ if (existing?._id) {
52
52
  return { captured: false, skipped: 'duplicate', id: existing._id, contentHash: contentHash };
53
53
  }
54
54
  // Új capture → a `write` create-úton (agent-provenance, auto-embed). A `source.path` a sessionId
@@ -215,7 +215,7 @@ class FAM_EmbeddingPipeline_ControlService {
215
215
  }
216
216
  /** A scope-szűkítő filter (a `scopePath` LEVÉL scopeId-jára). Üres scope → teljes tár (`!_deleted`). */
217
217
  buildScopeFilter(scopePath) {
218
- if (scopePath && scopePath.length) {
218
+ if (scopePath?.length) {
219
219
  const leaf = scopePath[scopePath.length - 1];
220
220
  const filter = { 'scopePath.scopeId': leaf.scopeId };
221
221
  return filter;
@@ -2,8 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FAM_Entry_DataService = void 0;
4
4
  const tslib_1 = require("tslib");
5
- const nts_dynamo_1 = require("@futdevpro/nts-dynamo");
6
5
  const mongoose_1 = tslib_1.__importDefault(require("mongoose"));
6
+ const nts_dynamo_1 = require("@futdevpro/nts-dynamo");
7
7
  const fam_entry_data_model_1 = require("../../../_models/data-models/fam-entry.data-model");
8
8
  /**
9
9
  * `FAM_Entry_DataService` (SP-2.2) — egy GENERIKUS, tár-paraméteres entry-CRUD a vektor-rétegnek.
@@ -195,5 +195,27 @@ class FAM_Entry_DataService extends nts_dynamo_1.DyNTS_DataService {
195
195
  filterBy['position.charStart'] = { $ne: position.charStart };
196
196
  await this.updateData({ filterBy: filterBy, update: update });
197
197
  }
198
+ /**
199
+ * A **git-repo provenance** (`source.repoUrl` / `repoName` / `repoRelativePath` / `repoBranch`) FELTÉTELES
200
+ * backfill-je (user-FR 2026-06-22) — a re-scan `equal` (változatlan-content) ágán hívva, hogy a CSAK-metaadat
201
+ * frissítés (a git-provenance feature ELŐTT ingestelt korpuszra) RE-EMBED NÉLKÜL megtörténjen. A hívó (ingest)
202
+ * adja a kiszámolt `git`-mezőket (a `FAM_GitRepo_Util`-t a control-service importálja — nincs modul-ciklus). Nem
203
+ * git-repo (nincs `repoUrl`) → no-op. Feltétel: a per-fájl `repoRelativePath` `$ne` (a Mongo no-op, ha már stimmel).
204
+ */
205
+ async backfillGitProvenance(id, git) {
206
+ if (!git.repoUrl) {
207
+ return;
208
+ }
209
+ const setBlock = {
210
+ ['source.repoUrl']: git.repoUrl,
211
+ ['source.repoName']: git.repoName,
212
+ ['source.repoRelativePath']: git.repoRelativePath,
213
+ ['source.repoBranch']: git.repoBranch,
214
+ };
215
+ const update = { $set: setBlock };
216
+ const filterBy = { _id: id };
217
+ filterBy['source.repoRelativePath'] = { $ne: git.repoRelativePath };
218
+ await this.updateData({ filterBy: filterBy, update: update });
219
+ }
198
220
  }
199
221
  exports.FAM_Entry_DataService = FAM_Entry_DataService;
@@ -373,6 +373,10 @@ class FAM_Ingest_ControlService {
373
373
  // re-embed nélkül (a friss chunk charStart/charEnd/sor-pozíciója felülír, ha eltér).
374
374
  await set.dataService.backfillPosition(item.existingId, item.chunk.position);
375
375
  }
376
+ // Git-provenance backfill (user-FR 2026-06-22): a CSAK-metaadat frissítés — a git-provenance
377
+ // feature ELŐTT ingestelt korpusz `equal`-chunkjai is megkapják a repoUrl/repoRelativePath/
378
+ // repoBranch-et RE-EMBED NÉLKÜL (a control-service számolja, így nincs data-service↔ingest ciklus).
379
+ await set.dataService.backfillGitProvenance(item.existingId, fam_git_repo_util_1.FAM_GitRepo_Util.sourceFieldsFor(set.file.absolutePath));
376
380
  }
377
381
  catch {
378
382
  // best-effort metaadat-frissítés — a content már naprakész (equal), nem eszkaláljuk.
@@ -479,7 +483,7 @@ class FAM_Ingest_ControlService {
479
483
  const existing = await dataService.findHydratableList(filter);
480
484
  const existingEntry = existing[0];
481
485
  // Változatlan fájl-lista → NO-OP (nincs fölös re-embed).
482
- if (existingEntry && existingEntry.contentHash === contentHash) {
486
+ if (existingEntry?.contentHash === contentHash) {
483
487
  return;
484
488
  }
485
489
  const entry = new fam_entry_data_model_1.FAM_Entry({
@@ -25,7 +25,7 @@ const MAIN_TABLE_ENUM = Object.values(fam_table_type_enum_1.FAM_Table).filter((t
25
25
  /** A nyers scope-lánc array-schema (`{ layer, rawName }[]`). */
26
26
  const SCOPE_LAYER_ARRAY = {
27
27
  type: 'array',
28
- description: 'Nyers scope-lánc (root→leaf): [{ layer, rawName }] párok (pl. organization=FutDevPro, project=Adventor).',
28
+ description: 'Nyers scope-lánc (root→leaf): [{ layer: layer, rawName: rawName }] párok (pl. organization=FutDevPro, project=Adventor).',
29
29
  items: {
30
30
  type: 'object',
31
31
  properties: {
@@ -347,7 +347,7 @@ class FAM_CapabilityRegistry_Service {
347
347
  });
348
348
  this.register({
349
349
  name: 'export_authored', category: category,
350
- description: 'FEAT-007: a non-file-derived (kézzel/agent-írt, `source.type ∈ {manual, agent}`) bejegyzések '
350
+ description: 'FEAT-007: a non-file-derived (kézzel/agent-írt, `source.type ∈ {manual: manual, agent: agent}`) bejegyzések '
351
351
  + 'exportja MINDEN tárból — a re-scan-nel NEM regenerálható, pótolhatatlan kurált érték biztonsági '
352
352
  + 'mentése (a `contentVector` nélkül; a hívó/CLI JSON-fájlba írja).',
353
353
  inputSchema: this.objectSchema({}),
@@ -393,6 +393,56 @@ class FAM_CapabilityRegistry_Service {
393
393
  }),
394
394
  handler: async (args) => this.ok(await this.manageReference(this.asObject(args))),
395
395
  });
396
+ this.register({
397
+ name: 'rename_scope', category: category,
398
+ description: 'Egy scope ÁTNEVEZÉSE (user-FR): a `canonicalName`-t a denormalizált scopePath-okon (MINDEN '
399
+ + 'RAG-entry) + a reference-en + a scope-doc-on bulk-ban átírja (a scopeId VÁLTOZATLAN). A scope '
400
+ + 'megadható `scopeId`-val VAGY `canonicalName`(+`layer`)-rel. **`dryRun:true` (DEFAULT) csak az érintett-'
401
+ + 'számot adja, NEM ír**; a tényleges íráshoz `dryRun:false` explicit. Nagy átírás után szerver-restart '
402
+ + 'ajánlott a LVS-pool friss scope-metaadatához.',
403
+ inputSchema: this.objectSchema({
404
+ scopeId: { type: 'string', description: 'A scope _id-ja (egyértelmű azonosítás).' },
405
+ layer: { type: 'string', description: 'Opcionális layer a canonicalName egyértelműsítéséhez.' },
406
+ canonicalName: { type: 'string', description: 'A scope JELENLEGI neve (scopeId helyett).' },
407
+ newName: { type: 'string', description: 'Az ÚJ canonicalName — KÖTELEZŐ.' },
408
+ dryRun: { type: 'boolean', description: 'true (default): csak preview; false: ír.' },
409
+ }),
410
+ handler: async (args) => {
411
+ const input = this.asObject(args);
412
+ return this.ok(await scope_reference_1.FAM_ScopeMaintenance_ControlService.getInstance().renameScope({
413
+ scopeId: this.asString(input.scopeId),
414
+ layer: this.asString(input.layer),
415
+ canonicalName: this.asString(input.canonicalName),
416
+ newName: this.asString(input.newName) ?? '',
417
+ dryRun: input.dryRun !== false,
418
+ }));
419
+ },
420
+ });
421
+ this.register({
422
+ name: 'merge_scopes', category: category,
423
+ description: 'Két scope ÖSSZEOLVASZTÁSA (user-FR): a `from` scope MINDEN entry-je az `into` scope-ra mutat '
424
+ + '(scopePath + reference átírva), majd a `from` scope soft-delete (az `into` MARAD). from/into megadható '
425
+ + '`...ScopeId`-val VAGY `...Name`(+`...Layer`)-rel. **`dryRun:true` (DEFAULT) csak az érintett-számot, NEM '
426
+ + 'ír**; `dryRun:false` ír. NEM dedup-ol (a content-hash-összevonás a `deduplicate_entries`). Nagy merge '
427
+ + 'után szerver-restart ajánlott.',
428
+ inputSchema: this.objectSchema({
429
+ fromScopeId: { type: 'string', description: 'A FORRÁS scope _id-ja.' },
430
+ fromName: { type: 'string', description: 'A forrás scope neve (scopeId helyett).' },
431
+ fromLayer: { type: 'string', description: 'Opcionális forrás-layer az egyértelműsítéshez.' },
432
+ intoScopeId: { type: 'string', description: 'A CÉL scope _id-ja (ebbe olvad).' },
433
+ intoName: { type: 'string', description: 'A cél scope neve (scopeId helyett).' },
434
+ intoLayer: { type: 'string', description: 'Opcionális cél-layer az egyértelműsítéshez.' },
435
+ dryRun: { type: 'boolean', description: 'true (default): csak preview; false: ír.' },
436
+ }),
437
+ handler: async (args) => {
438
+ const input = this.asObject(args);
439
+ return this.ok(await scope_reference_1.FAM_ScopeMaintenance_ControlService.getInstance().mergeScopes({
440
+ from: { scopeId: this.asString(input.fromScopeId), canonicalName: this.asString(input.fromName), layer: this.asString(input.fromLayer) },
441
+ into: { scopeId: this.asString(input.intoScopeId), canonicalName: this.asString(input.intoName), layer: this.asString(input.intoLayer) },
442
+ dryRun: input.dryRun !== false,
443
+ }));
444
+ },
445
+ });
396
446
  // repair_scope_links / deduplicate_entries (MP-12) — DRY-RUN-ONLY MVP1 (safe). A destruktív
397
447
  // write-ág (entry-mutáció / soft-delete-összevonás) BACKLOG: vak destruktív bulk-op TILOS
398
448
  // (dsgn-003 §4.2), ezért a `dryRun:false` kérés clear "BACKLOG" hibát ad — a `dryRun:true` (default)
@@ -457,7 +507,7 @@ class FAM_CapabilityRegistry_Service {
457
507
  this.register({
458
508
  name: 'import_claude_mem', category: category,
459
509
  description: 'A claude-mem migráció tényleges beírása egy import-batch (batchId) alatt: dedup-skip + '
460
- + 'map + persist + embed. Output: { ok, batchId, ingested, skipped, failed, byTable, status } (dsgn-009 §5.3).',
510
+ + 'map + persist + embed. Output: { ok: ok, batchId: batchId, ingested: ingested, skipped: skipped, failed: failed, byTable: byTable, status: status } (dsgn-009 §5.3).',
461
511
  inputSchema: this.importMapSchema(), handler: runHandler,
462
512
  });
463
513
  this.register({
@@ -556,7 +606,10 @@ class FAM_CapabilityRegistry_Service {
556
606
  // =========================================================================
557
607
  // Wired handler-helpers (delegálás az engine-ekre)
558
608
  // =========================================================================
559
- /** Per-tár statisztika (aktív elem-szám + embedding-státusz bontás) — MP-1. */
609
+ /**
610
+ * Per-tár statisztika (aktív elem-szám + embedding-státusz bontás) — MP-1.
611
+ * FIX (OOM): `countDocuments` + `$group`-aggregáció — NEM tölti a doc-okat memóriába (a 126k-vektoros load OOM-ozott).
612
+ */
560
613
  async tableStats(table) {
561
614
  const tables = table ? [table] : MAIN_TABLES;
562
615
  const result = [];
@@ -565,14 +618,22 @@ class FAM_CapabilityRegistry_Service {
565
618
  if (!registryEntry) {
566
619
  continue;
567
620
  }
568
- const dataService = new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
569
- const entries = await dataService.findHydratableList({});
621
+ const collection = this.entryCollection(registryEntry);
622
+ const count = await collection.countDocuments({});
570
623
  const byStatus = { pending: 0, completed: 0, error: 0 };
571
- for (const entry of entries) {
572
- const status = entry.embeddingStatus ?? 'pending';
573
- byStatus[status] = (byStatus[status] ?? 0) + 1;
624
+ const grouped = await collection
625
+ .aggregate([{ $group: { _id: '$embeddingStatus', n: { $sum: 1 } } }])
626
+ .toArray();
627
+ for (const group of grouped) {
628
+ const status = group._id ?? 'pending';
629
+ if (status in byStatus) {
630
+ byStatus[status] += group.n;
631
+ }
632
+ else {
633
+ byStatus.pending += group.n;
634
+ }
574
635
  }
575
- result.push({ table: target, count: entries.length, embeddingStatus: byStatus });
636
+ result.push({ table: target, count: count, embeddingStatus: byStatus });
576
637
  }
577
638
  return table ? result[0] ?? { table: table, count: 0 } : result;
578
639
  }
@@ -618,7 +679,7 @@ class FAM_CapabilityRegistry_Service {
618
679
  /** Egy env-változó jelenlét-ellenőrzése (`treatDefaultAsPresent`: van builtin default, ezért sosem hiányzik). */
619
680
  checkEnv(name, hint, present, missing, treatDefaultAsPresent = false) {
620
681
  const value = process.env[name];
621
- if ((value && value.trim().length) || treatDefaultAsPresent) {
682
+ if ((value?.trim().length) || treatDefaultAsPresent) {
622
683
  present.push(name);
623
684
  return;
624
685
  }
@@ -750,11 +811,10 @@ class FAM_CapabilityRegistry_Service {
750
811
  */
751
812
  async storageUsage() {
752
813
  const collections = [];
753
- // Fő RAG-tárak (a store-registry-n át, a per-tár dataParams-szal).
814
+ // Fő RAG-tárak — FIX (OOM): `countDocuments` (NEM a doc-ok memóriába töltése; a 126k-vektoros load OOM-ozott).
754
815
  for (const registryEntry of embedding_1.FAM_STORE_REGISTRY) {
755
- const dataService = new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
756
- const entries = await dataService.findHydratableList({});
757
- collections.push({ collection: this.realCollectionName(registryEntry.dataParams.dataName), activeDocs: entries.length });
816
+ const activeDocs = await this.entryCollection(registryEntry).countDocuments({});
817
+ collections.push({ collection: this.realCollectionName(registryEntry.dataParams.dataName), activeDocs: activeDocs });
758
818
  }
759
819
  // Helper collection-ök (reference / scope / ingest-run / config) — a DS-példányosítás regisztrálja a modellt.
760
820
  const reference_DS = new scope_reference_1.FAM_Reference_DataService({ issuer: this.issuer });
@@ -865,21 +925,24 @@ class FAM_CapabilityRegistry_Service {
865
925
  const tables = table ? [table] : MAIN_TABLES;
866
926
  const result = [];
867
927
  for (const target of tables) {
868
- const entries = await this.loadTableEntries(target);
869
- const kindCounts = new Map();
870
- const tagCounts = new Map();
871
- for (const entry of entries) {
872
- if (entry.kind) {
873
- kindCounts.set(entry.kind, (kindCounts.get(entry.kind) ?? 0) + 1);
874
- }
875
- for (const tag of entry.tags ?? []) {
876
- tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
877
- }
928
+ const registryEntry = embedding_1.FAM_STORE_REGISTRY.find((entry) => entry.table === target);
929
+ if (!registryEntry) {
930
+ continue;
878
931
  }
932
+ // FIX (OOM): Mongo-aggregáció ($group kind / $unwind+$group tag) — NEM tölti a doc-okat memóriába.
933
+ const collection = this.entryCollection(registryEntry);
934
+ const kindAgg = await collection
935
+ .aggregate([
936
+ { $match: { kind: { $ne: null } } }, { $group: { _id: '$kind', n: { $sum: 1 } } }, { $sort: { n: -1 } },
937
+ ]).toArray();
938
+ const tagAgg = await collection
939
+ .aggregate([
940
+ { $unwind: '$tags' }, { $group: { _id: '$tags', n: { $sum: 1 } } }, { $sort: { n: -1 } },
941
+ ]).toArray();
879
942
  result.push({
880
943
  table: target,
881
- kinds: this.sortedCounts(kindCounts).map((entry) => ({ kind: entry.label, count: entry.count })),
882
- tags: this.sortedCounts(tagCounts).map((entry) => ({ tag: entry.label, count: entry.count })),
944
+ kinds: kindAgg.filter((group) => group._id).map((group) => ({ kind: group._id, count: group.n })),
945
+ tags: tagAgg.filter((group) => group._id).map((group) => ({ tag: group._id, count: group.n })),
883
946
  });
884
947
  }
885
948
  return result;
@@ -890,8 +953,17 @@ class FAM_CapabilityRegistry_Service {
890
953
  const tables = table ? [table] : MAIN_TABLES;
891
954
  const collected = [];
892
955
  for (const target of tables) {
893
- const entries = await this.loadTableEntries(target);
894
- for (const entry of entries) {
956
+ const registryEntry = embedding_1.FAM_STORE_REGISTRY.find((entry) => entry.table === target);
957
+ if (!registryEntry) {
958
+ continue;
959
+ }
960
+ // FIX (OOM): per-tár BOUNDED query (`sort __created` + `limit`), NEM a teljes tár betöltése N elemért.
961
+ const docs = await this.entryCollection(registryEntry)
962
+ .find({}, { projection: { kind: 1, tags: 1, __created: 1, content: 1 } })
963
+ .sort({ __created: -1 })
964
+ .limit(cappedLimit)
965
+ .toArray();
966
+ for (const entry of docs) {
895
967
  if (!entry._id) {
896
968
  continue;
897
969
  }
@@ -1083,14 +1155,22 @@ class FAM_CapabilityRegistry_Service {
1083
1155
  // =========================================================================
1084
1156
  // MP-12 közös entry-helperek (lazy DataService — eager-resolve elkerülés)
1085
1157
  // =========================================================================
1086
- /** Egy fő tár aktív entry-i (a per-tár dataParams-szal). Nem-fő/ismeretlen tár → üres. */
1158
+ /**
1159
+ * Egy fő tár aktív entry-i (a per-tár dataParams-szal). Nem-fő/ismeretlen tár → üres.
1160
+ * FIX (OOM): a nagy `contentVector` (2560 float/doc) PROJEKCIÓVAL KIHAGYVA — a fogyasztók (kinds/recent/repair/
1161
+ * dedup) NEM használják; e nélkül a teljes-tár betöltés ~20×-kal kisebb (a 126k vektor a heap-OOM forrása volt).
1162
+ */
1087
1163
  async loadTableEntries(table) {
1088
1164
  const registryEntry = embedding_1.FAM_STORE_REGISTRY.find((entry) => entry.table === table);
1089
1165
  if (!registryEntry) {
1090
1166
  return [];
1091
1167
  }
1092
- const dataService = new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
1093
- return dataService.findHydratableList({});
1168
+ return this.entryCollection(registryEntry).find({}, { projection: { contentVector: 0 } }).toArray();
1169
+ }
1170
+ /** Egy fő RAG-tár mongoose-collection-je (a DS-példányosítás regisztrálja a modellt; onnan a valós collection). */
1171
+ entryCollection(registryEntry) {
1172
+ new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
1173
+ return mongoose_1.default.models[registryEntry.dataParams.dataName].collection;
1094
1174
  }
1095
1175
  /** Egy entry feloldása `id`-ből (a megadott tárból, vagy minden fő tárból). Nincs → `FAM-MCP-DISPATCH-002`. */
1096
1176
  async requireEntryById(id, table, capability) {
@@ -1110,7 +1190,7 @@ class FAM_CapabilityRegistry_Service {
1110
1190
  }
1111
1191
  const dataService = new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
1112
1192
  const entry = await dataService.findData({ _id: id }, true);
1113
- if (entry && entry._id) {
1193
+ if (entry?._id) {
1114
1194
  return entry;
1115
1195
  }
1116
1196
  }
@@ -1190,7 +1270,7 @@ class FAM_CapabilityRegistry_Service {
1190
1270
  scopeLayerArraySchema() {
1191
1271
  return {
1192
1272
  type: 'array',
1193
- description: 'Nyers scope-lánc (root→leaf): [{ layer, rawName }] párok.',
1273
+ description: 'Nyers scope-lánc (root→leaf): [{ layer: layer, rawName: rawName }] párok.',
1194
1274
  items: this.objectSchema({
1195
1275
  layer: { type: 'string', description: 'A dinamikus layer-név (pl. organization/project).' },
1196
1276
  rawName: { type: 'string', description: 'A nyers (feloldatlan) scope-név.' },
@@ -98,7 +98,7 @@ class FAM_ReadTool_Service {
98
98
  throw fam_error_factory_util_1.FAM_Error_Util.create({
99
99
  errorCode: error_codes_const_1.FAM_ERROR_CODES.valReadEmptyOrReference,
100
100
  message: 'A `read` legalább egy query-t igényel (`queries[]`). Adj meg legalább egy '
101
- + '{ tables, query } blokkot.',
101
+ + '{ tables: tables, query: query } blokkot.',
102
102
  issuer: this.issuer,
103
103
  context: { operation: 'read-validate' },
104
104
  });
@@ -417,7 +417,7 @@ class FAM_ClaudeMemImport_ControlService {
417
417
  }
418
418
  const overrideKey = `project:${record.project}`;
419
419
  const override = request.overrides?.scope?.[overrideKey];
420
- if (override && override.scopePath.length) {
420
+ if (override?.scopePath.length) {
421
421
  return override.scopePath.map((layer) => ({ layer: layer.layer, rawName: layer.rawName }));
422
422
  }
423
423
  return [{ layer: PROJECT_SCOPE_LAYER, rawName: record.project }];
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAM_ScopeMaintenance_Util = void 0;
4
+ /**
5
+ * `FAM_ScopeMaintenance_Util` (user-FR 2026-06-22) — a **scope rename/merge** Mongo-update-specjeinek
6
+ * TISZTA (pure) felépítője. A scope-azonosító a denormalizált `scopePath[]`-en (minden RAG-entry) ÉS a
7
+ * reference `canonicalScopeRef`-jén (egyetlen objektum) szerepel; a rename/merge ezeket írja át bulk-ban.
8
+ *
9
+ * **Rename** = a `scopeId`-hez tartozó `canonicalName` átírása (a scopeId marad). **Merge** = a `from`
10
+ * scopeId minden előfordulása az `into` scope `{scopeId, layer, canonicalName}`-jére cserélve (majd a
11
+ * hívó soft-delete-eli a `from` scope-ot). A spec-építés determinisztikus + külön tesztelhető; az
12
+ * alkalmazás (mongoose `updateMany`) a control-service-ben.
13
+ */
14
+ class FAM_ScopeMaintenance_Util {
15
+ /** RENAME — entry-collection (`scopePath[]`): a `scopeId`-elem `canonicalName`-jét `newName`-re. */
16
+ static renameEntryUpdate(scopeId, newName) {
17
+ return {
18
+ filter: { 'scopePath.scopeId': scopeId },
19
+ update: { $set: { 'scopePath.$[el].canonicalName': newName } },
20
+ arrayFilters: [{ 'el.scopeId': scopeId }],
21
+ };
22
+ }
23
+ /** MERGE — entry-collection (`scopePath[]`): a `fromId`-elemet az `into` ref `{scopeId,layer,canonicalName}`-jére. */
24
+ static mergeEntryUpdate(fromId, into) {
25
+ return {
26
+ filter: { 'scopePath.scopeId': fromId },
27
+ update: {
28
+ $set: {
29
+ 'scopePath.$[el].scopeId': into.scopeId,
30
+ 'scopePath.$[el].layer': into.layer,
31
+ 'scopePath.$[el].canonicalName': into.canonicalName,
32
+ },
33
+ },
34
+ arrayFilters: [{ 'el.scopeId': fromId }],
35
+ };
36
+ }
37
+ /** RENAME — reference (`canonicalScopeRef`, egyetlen objektum): a `canonicalName` átírása. */
38
+ static renameReferenceUpdate(scopeId, newName) {
39
+ return {
40
+ filter: { 'canonicalScopeRef.scopeId': scopeId },
41
+ update: { $set: { 'canonicalScopeRef.canonicalName': newName } },
42
+ };
43
+ }
44
+ /** MERGE — reference (`canonicalScopeRef`): a `from` ref cseréje az `into`-ra. */
45
+ static mergeReferenceUpdate(fromId, into) {
46
+ return {
47
+ filter: { 'canonicalScopeRef.scopeId': fromId },
48
+ update: {
49
+ $set: {
50
+ 'canonicalScopeRef.scopeId': into.scopeId,
51
+ 'canonicalScopeRef.layer': into.layer,
52
+ 'canonicalScopeRef.canonicalName': into.canonicalName,
53
+ },
54
+ },
55
+ };
56
+ }
57
+ }
58
+ exports.FAM_ScopeMaintenance_Util = FAM_ScopeMaintenance_Util;
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAM_ScopeMaintenance_ControlService = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const mongoose_1 = tslib_1.__importDefault(require("mongoose"));
6
+ const error_codes_const_1 = require("../../../_collections/error-codes.const");
7
+ const fam_error_factory_util_1 = require("../../../_collections/fam-error-factory.util");
8
+ const embedding_1 = require("../../embedding");
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
+ const fam_scope_maintenance_util_1 = require("../_collections/fam-scope-maintenance.util");
13
+ /**
14
+ * `FAM_ScopeMaintenance_ControlService` (user-FR 2026-06-22) — **scope rename + merge** maintenance-engine.
15
+ * A claude-mem import (és egyéb) rendetlen scope-neveit (pl. `10.3.3` → valójában ccap-revisioned) rendezi:
16
+ * a `canonicalName`-t a denormalizált `scopePath[]`-en (MINDEN RAG-entry) + a reference `canonicalScopeRef`-jén
17
+ * + a scope-doc-on bulk-ban átírja (rename), VAGY egy scope-ot egy másikba olvaszt (merge → a forrás-scope
18
+ * minden entry-je az `into`-ra mutat, majd a forrás-scope soft-delete).
19
+ *
20
+ * **dryRun (default true a destruktív-biztonsághoz):** csak az érintett-számokat adja vissza, NEM ír. A
21
+ * tényleges íráshoz `dryRun:false` explicit. A bulk array-update mongoose `updateMany` + `arrayFilters` (a spec
22
+ * a tiszta `FAM_ScopeMaintenance_Util`-ból). A LVS-pool a Mongo-ból hidratál → a változás a következő (vagy
23
+ * scope-scoped) keresésnél él; nagy átírás után szerver-restart ajánlott a pool friss scope-metaadatához.
24
+ *
25
+ * **Megjegyzés (dedup):** a merge NEM dedup-ol — ha az `into` és a `from` ugyanazt a tartalmat tartalmazta,
26
+ * a `deduplicate_entries` (külön capability) felel a content-hash-összevonásért.
27
+ */
28
+ class FAM_ScopeMaintenance_ControlService {
29
+ static _instance;
30
+ issuer = 'FAM_ScopeMaintenance_ControlService';
31
+ static getInstance() {
32
+ if (!FAM_ScopeMaintenance_ControlService._instance) {
33
+ FAM_ScopeMaintenance_ControlService._instance = new FAM_ScopeMaintenance_ControlService();
34
+ }
35
+ return FAM_ScopeMaintenance_ControlService._instance;
36
+ }
37
+ /**
38
+ * Egy scope ÁTNEVEZÉSE: a `scopeId` `canonicalName`-jét `newName`-re — a scopeId VÁLTOZATLAN, csak a
39
+ * denormalizált név (entry-k `scopePath` + reference + scope-doc). `dryRun` → csak az érintett-szám.
40
+ */
41
+ async renameScope(set) {
42
+ const newName = (set.newName ?? '').trim();
43
+ if (!newName) {
44
+ throw this.invalid('A `newName` (nem-üres string) KÖTELEZŐ a rename_scope-hoz.', 'rename_scope');
45
+ }
46
+ const scope = await this.resolveScope(set);
47
+ const scopeId = scope._id;
48
+ const dryRun = set.dryRun !== false;
49
+ const affected = await this.countAffected(scopeId);
50
+ if (!dryRun) {
51
+ const entryUpdate = fam_scope_maintenance_util_1.FAM_ScopeMaintenance_Util.renameEntryUpdate(scopeId, newName);
52
+ await this.applyEntryUpdate(entryUpdate);
53
+ const refUpdate = fam_scope_maintenance_util_1.FAM_ScopeMaintenance_Util.renameReferenceUpdate(scopeId, newName);
54
+ await this.referenceCollection().collection.updateMany(refUpdate.filter, refUpdate.update);
55
+ // A scope-doc canonicalName-je ($set, atomikus — a denormalizált forrás).
56
+ await new fam_scope_data_service_1.FAM_Scope_DataService({ issuer: this.issuer })
57
+ .updateData({ filterBy: { _id: scopeId }, update: { $set: { canonicalName: newName } } });
58
+ }
59
+ return {
60
+ operation: 'rename_scope',
61
+ dryRun: dryRun,
62
+ scope: { scopeId: scopeId, layer: scope.layer ?? '', oldName: scope.canonicalName ?? '', newName: newName },
63
+ affected: affected,
64
+ totalAffected: affected.reduce((sum, item) => sum + item.count, 0),
65
+ };
66
+ }
67
+ /**
68
+ * Két scope ÖSSZEOLVASZTÁSA: a `from` scope minden entry-je az `into` scope `{scopeId,layer,canonicalName}`-jére
69
+ * mutat, majd a `from` scope soft-delete. `dryRun` → csak az érintett-szám. Az `into` MARAD.
70
+ */
71
+ async mergeScopes(set) {
72
+ const from = await this.resolveScope(set.from);
73
+ const into = await this.resolveScope(set.into);
74
+ const fromId = from._id;
75
+ const intoId = into._id;
76
+ if (fromId === intoId) {
77
+ throw this.invalid('A `from` és az `into` ugyanaz a scope — nincs mit összeolvasztani.', 'merge_scopes');
78
+ }
79
+ const dryRun = set.dryRun !== false;
80
+ const intoRef = { scopeId: intoId, layer: into.layer ?? '', canonicalName: into.canonicalName ?? '' };
81
+ const affected = await this.countAffected(fromId);
82
+ if (!dryRun) {
83
+ const entryUpdate = fam_scope_maintenance_util_1.FAM_ScopeMaintenance_Util.mergeEntryUpdate(fromId, intoRef);
84
+ await this.applyEntryUpdate(entryUpdate);
85
+ const refUpdate = fam_scope_maintenance_util_1.FAM_ScopeMaintenance_Util.mergeReferenceUpdate(fromId, intoRef);
86
+ await this.referenceCollection().collection.updateMany(refUpdate.filter, refUpdate.update);
87
+ // A forrás-scope soft-delete (move-to-archive) — már nincs rá hivatkozó entry.
88
+ await new fam_scope_data_service_1.FAM_Scope_DataService({ issuer: this.issuer }).deleteData(fromId);
89
+ }
90
+ return {
91
+ operation: 'merge_scopes',
92
+ dryRun: dryRun,
93
+ from: { scopeId: fromId, layer: from.layer ?? '', canonicalName: from.canonicalName ?? '' },
94
+ into: { scopeId: intoId, layer: into.layer ?? '', canonicalName: into.canonicalName ?? '' },
95
+ affected: affected,
96
+ totalAffected: affected.reduce((sum, item) => sum + item.count, 0),
97
+ fromScopeDeleted: !dryRun,
98
+ };
99
+ }
100
+ /** Az `entryUpdate` spec alkalmazása MINDEN fő RAG-tár collection-jén (mongoose `updateMany` + arrayFilters). */
101
+ async applyEntryUpdate(entryUpdate) {
102
+ for (const named of this.entryCollections()) {
103
+ await named.collection.updateMany(entryUpdate.filter, entryUpdate.update, { arrayFilters: entryUpdate.arrayFilters });
104
+ }
105
+ }
106
+ /** A `scopeId`-t hivatkozó aktív entry-k száma per-collection (a 6 fő tár + a reference). */
107
+ async countAffected(scopeId) {
108
+ const affected = [];
109
+ for (const named of this.entryCollections()) {
110
+ affected.push({ collection: named.name, count: await named.collection.countDocuments({ 'scopePath.scopeId': scopeId }) });
111
+ }
112
+ const reference = this.referenceCollection();
113
+ affected.push({ collection: reference.name, count: await reference.collection.countDocuments({ 'canonicalScopeRef.scopeId': scopeId }) });
114
+ return affected;
115
+ }
116
+ /** A 6 fő RAG-tár mongoose-collection-jei (a DS-példányosítás regisztrálja a modellt, onnan a valós collection). */
117
+ entryCollections() {
118
+ const result = [];
119
+ for (const entry of embedding_1.FAM_STORE_REGISTRY) {
120
+ new embedding_1.FAM_Entry_DataService({ dataParams: entry.dataParams, issuer: this.issuer });
121
+ const model = mongoose_1.default.models[entry.dataParams.dataName];
122
+ if (model?.collection) {
123
+ result.push({ name: model.collection.collectionName, collection: model.collection });
124
+ }
125
+ }
126
+ return result;
127
+ }
128
+ /** A reference helper-tár mongoose-collection-je. */
129
+ referenceCollection() {
130
+ new fam_reference_data_service_1.FAM_Reference_DataService({ issuer: this.issuer });
131
+ const model = mongoose_1.default.models['fam_reference'];
132
+ return { name: model?.collection?.collectionName ?? 'fam_references', collection: model.collection };
133
+ }
134
+ /**
135
+ * Egy scope feloldása a szelektorból: `scopeId` (közvetlen) VAGY `layer`+`canonicalName` (egyértelmű) VAGY
136
+ * csak `canonicalName` (minden layer-en, normalizált-egyezés — egyértelmű kell legyen). Nem-talált/kétértelmű → hiba.
137
+ */
138
+ async resolveScope(selector) {
139
+ const dataService = new fam_scope_data_service_1.FAM_Scope_DataService({ issuer: this.issuer });
140
+ if (selector.scopeId) {
141
+ const scope = await dataService.findScopeById(selector.scopeId);
142
+ if (scope?._id) {
143
+ return scope;
144
+ }
145
+ throw this.notFound(`scopeId '${selector.scopeId}'`);
146
+ }
147
+ if (selector.canonicalName) {
148
+ const matches = selector.layer
149
+ ? await dataService.findByCanonical(selector.layer, selector.canonicalName)
150
+ : await this.findByNameAnyLayer(dataService, selector.canonicalName);
151
+ if (matches.length === 1) {
152
+ return matches[0];
153
+ }
154
+ if (matches.length > 1) {
155
+ throw fam_error_factory_util_1.FAM_Error_Util.create({
156
+ errorCode: error_codes_const_1.FAM_ERROR_CODES.refResolveAmbiguous,
157
+ message: `A(z) '${selector.canonicalName}' név több scope-ra illeszkedik (${matches.length}) — adj meg `
158
+ + '`layer`-t vagy `scopeId`-t az egyértelműsítéshez.',
159
+ issuer: this.issuer,
160
+ context: { operation: 'scope-maintenance:resolve' },
161
+ });
162
+ }
163
+ throw this.notFound(`'${selector.canonicalName}'${selector.layer ? ` (layer '${selector.layer}')` : ''}`);
164
+ }
165
+ throw this.invalid('Add meg a scope-ot: `scopeId` VAGY `canonicalName` (+ opcionális `layer`).', 'resolve-scope');
166
+ }
167
+ /** Egy név normalizált-egyezése MINDEN aktív scope-on (layer nélkül; a halmaz kicsi). */
168
+ async findByNameAnyLayer(dataService, name) {
169
+ const all = await dataService.findAllActive();
170
+ const target = fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(name);
171
+ return all.filter((scope) => fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(scope.canonicalName ?? '') === target);
172
+ }
173
+ notFound(what) {
174
+ return fam_error_factory_util_1.FAM_Error_Util.create({
175
+ errorCode: error_codes_const_1.FAM_ERROR_CODES.refResolveNoMatch,
176
+ message: `Nincs aktív scope: ${what}.`,
177
+ issuer: this.issuer,
178
+ context: { operation: 'scope-maintenance:resolve' },
179
+ });
180
+ }
181
+ invalid(message, operation) {
182
+ return fam_error_factory_util_1.FAM_Error_Util.create({
183
+ errorCode: error_codes_const_1.FAM_ERROR_CODES.mcpDispatchUnknownCapability,
184
+ message: message,
185
+ issuer: this.issuer,
186
+ context: { operation: `scope-maintenance:${operation}` },
187
+ });
188
+ }
189
+ }
190
+ exports.FAM_ScopeMaintenance_ControlService = FAM_ScopeMaintenance_ControlService;
@@ -409,7 +409,7 @@ class FAM_ScopeResolver_ControlService {
409
409
  }
410
410
  const scope_DS = new fam_scope_data_service_1.FAM_Scope_DataService({ issuer: this.issuer });
411
411
  const scope = await scope_DS.findScopeById(scopeId);
412
- return Boolean(scope && scope._id);
412
+ return Boolean(scope?._id);
413
413
  }
414
414
  // =========================================================================
415
415
  // TRACE (dsgn-002 §4, dsgn-003 §4.2) — a debug-capability (MP-6) fogyasztja
@@ -460,7 +460,7 @@ class FAM_ScopeResolver_ControlService {
460
460
  async traceEntityFallback(rawLayer, scopeDataService) {
461
461
  const matches = await scopeDataService.findByCanonical(rawLayer.layer, rawLayer.rawName);
462
462
  const hit = matches.find((scope) => !!scope._id);
463
- if (hit && hit._id) {
463
+ if (hit?._id) {
464
464
  return { matched: true, scopeId: hit._id, canonicalName: hit.canonicalName };
465
465
  }
466
466
  return { matched: false };
@@ -9,7 +9,7 @@
9
9
  * a fogyasztó-MP-ké. A reference/scope collection-ök MP-1-ben már a `dbModels`-ben vannak.
10
10
  */
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.FAM_FuzzyMatch_Util = exports.FAM_ScopeNormalize_Util = exports.FAM_ScopeResolver_ControlService = exports.FAM_Reference_DataService = exports.FAM_Scope_DataService = void 0;
12
+ exports.FAM_FuzzyMatch_Util = exports.FAM_ScopeNormalize_Util = exports.FAM_ScopeMaintenance_ControlService = exports.FAM_ScopeResolver_ControlService = exports.FAM_Reference_DataService = exports.FAM_Scope_DataService = void 0;
13
13
  // SP-3.1 — Scope CRUD + scope-fa traverzál
14
14
  var fam_scope_data_service_1 = require("./_services/fam-scope.data-service");
15
15
  Object.defineProperty(exports, "FAM_Scope_DataService", { enumerable: true, get: function () { return fam_scope_data_service_1.FAM_Scope_DataService; } });
@@ -19,6 +19,9 @@ Object.defineProperty(exports, "FAM_Reference_DataService", { enumerable: true,
19
19
  // SP-3.3/SP-3.4 — a feloldó control-service (write-resolve / read-expand / cache / scopeExists / uncertainty)
20
20
  var fam_scope_resolver_control_service_1 = require("./_services/fam-scope-resolver.control-service");
21
21
  Object.defineProperty(exports, "FAM_ScopeResolver_ControlService", { enumerable: true, get: function () { return fam_scope_resolver_control_service_1.FAM_ScopeResolver_ControlService; } });
22
+ // user-FR 2026-06-22 — scope rename/merge maintenance (rendetlen claude-mem import scope-nevek rendezése)
23
+ var fam_scope_maintenance_control_service_1 = require("./_services/fam-scope-maintenance.control-service");
24
+ Object.defineProperty(exports, "FAM_ScopeMaintenance_ControlService", { enumerable: true, get: function () { return fam_scope_maintenance_control_service_1.FAM_ScopeMaintenance_ControlService; } });
22
25
  // utils (SP-3.1/SP-3.2)
23
26
  var fam_scope_normalize_util_1 = require("./_collections/fam-scope-normalize.util");
24
27
  Object.defineProperty(exports, "FAM_ScopeNormalize_Util", { enumerable: true, get: function () { return fam_scope_normalize_util_1.FAM_ScopeNormalize_Util; } });
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Api_Controller = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const mongoose_1 = tslib_1.__importDefault(require("mongoose"));
4
6
  const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
5
7
  const nts_dynamo_1 = require("@futdevpro/nts-dynamo");
8
+ const nts_dynamo_2 = require("@futdevpro/nts-dynamo");
6
9
  const fam_version_const_1 = require("../../../_collections/fam-version.const");
7
- const fam_heap_guard_control_service_1 = require("../../../_collections/fam-heap-guard.control-service");
8
10
  const fam_table_type_enum_1 = require("../../../_enums/fam-table.type-enum");
9
11
  const fam_config_level_type_enum_1 = require("../../../_enums/fam-config-level.type-enum");
10
12
  const config_control_service_1 = require("../config/config.control-service");
@@ -141,12 +143,12 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
141
143
  endpoint: '/health',
142
144
  tasks: [
143
145
  async (req, res) => {
144
- // A heap-nyomás IS kimegy a health-en (observability — a CLI/UI/agent látja a memória-állapotot).
145
- const memory = fam_heap_guard_control_service_1.FAM_HeapGuard_ControlService.getInstance().pressure();
146
+ // A heap-nyomás IS kimegy a health-en (observability) — a BEDROCK DyNTS_MemoryGuard pressure-éből.
147
+ const memory = nts_dynamo_2.DyNTS_MemoryGuard.getInstance().pressure();
146
148
  res.send({
147
149
  ok: true, service: 'fdp-agent-memory', version: fam_version_const_1.FAM_VERSION, uptime: process.uptime(),
148
- memory: { level: memory.level, usedMb: memory.usedMb, limitMb: memory.limitMb,
149
- ratio: Math.round(memory.ratio * 100) / 100, gcFraction: Math.round(memory.gcFraction * 100) / 100 },
150
+ memory: { level: memory.level, heapUsedMb: memory.heapUsedMb, heapLimitMb: memory.heapLimitMb,
151
+ heapPct: memory.heapPct, rssMb: memory.rssMb, gcFraction: memory.gcFraction },
150
152
  });
151
153
  },
152
154
  ],
@@ -193,13 +195,27 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
193
195
  if (req.query.ingestRunId) {
194
196
  filter.ingestRunId = String(req.query.ingestRunId);
195
197
  }
196
- const dataService = new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
197
- const all = await dataService.findHydratableList(filter);
198
198
  const pageSize = Math.min(this.parseNumber(req.query.pageSize, DEFAULT_PAGE_SIZE), MAX_PAGE_SIZE);
199
199
  const page = Math.max(this.parseNumber(req.query.page, 1), 1);
200
200
  const start = (page - 1) * pageSize;
201
- const items = all.slice(start, start + pageSize);
202
- return { table: table, total: all.length, page: page, pageSize: pageSize, items: items };
201
+ // FIX (OOM): DB-OLDALI lapozás + `contentVector` projekció-kizárás. KORÁBBAN `findHydratableList({})` a TELJES
202
+ // tárat (126k+ teljes doc + 2560-dim vektor) memóriába töltötte ÉS memóriában szeletelt → a UI-polling többször
203
+ // GB-okat allokált → OOM + Mongo-flapping. Most: `countDocuments` + `skip/limit` + a nagy vektor-mező kihagyva.
204
+ const collection = this.entryCollection(registryEntry);
205
+ const total = await collection.countDocuments(filter);
206
+ const items = await collection
207
+ .find(filter, { projection: { contentVector: 0 } })
208
+ .sort({ __lastModified: -1 })
209
+ .skip(start)
210
+ .limit(pageSize)
211
+ .toArray();
212
+ return { table: table, total: total, page: page, pageSize: pageSize, items: items };
213
+ }
214
+ /** Egy fő RAG-tár mongoose-collection-je (a DS-példányosítás regisztrálja a modellt; onnan a valós collection). */
215
+ entryCollection(registryEntry) {
216
+ new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
217
+ const model = mongoose_1.default.models[registryEntry.dataParams.dataName];
218
+ return model.collection;
203
219
  }
204
220
  // =========================================================================
205
221
  // /stats · /stats/:table — high-level stats aggregátum (dsgn-011 §3/§4.4)
@@ -234,7 +250,11 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
234
250
  ],
235
251
  });
236
252
  }
237
- /** A tár-stats aggregátum (aktív elem-szám + embedding-státusz + utolsó ingest) — dsgn-011 §3/§4.4. */
253
+ /**
254
+ * A tár-stats aggregátum (aktív elem-szám + embedding-státusz + utolsó ingest) — dsgn-011 §3/§4.4.
255
+ * FIX (OOM): `countDocuments` + `$group`-aggregáció + 1-doc last-modified projekció — KORÁBBAN `findHydratableList({})`
256
+ * a TELJES tárat memóriába töltötte (a UI stats-pollingja → ismételt GB-allokáció → OOM). Most semmit nem hidratál.
257
+ */
238
258
  async collectTableStats(tables) {
239
259
  const result = [];
240
260
  for (const table of tables) {
@@ -242,24 +262,35 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
242
262
  if (!registryEntry) {
243
263
  continue;
244
264
  }
245
- const dataService = new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
246
- const entries = await dataService.findHydratableList({});
265
+ const collection = this.entryCollection(registryEntry);
266
+ const count = await collection.countDocuments({});
267
+ // Embedding-státusz bontás Mongo-aggregációval ($group), NEM a doc-ok memóriába töltésével.
247
268
  const byStatus = { pending: 0, completed: 0, error: 0 };
248
- let lastModifiedMs = 0;
249
- for (const entry of entries) {
250
- const status = entry.embeddingStatus ?? 'pending';
251
- byStatus[status] = (byStatus[status] ?? 0) + 1;
252
- const modified = entry.__lastModified ? new Date(entry.__lastModified).getTime() : 0;
253
- if (modified > lastModifiedMs) {
254
- lastModifiedMs = modified;
269
+ const grouped = await collection
270
+ .aggregate([{ $group: { _id: '$embeddingStatus', n: { $sum: 1 } } }])
271
+ .toArray();
272
+ for (const group of grouped) {
273
+ const status = group._id ?? 'pending';
274
+ if (status in byStatus) {
275
+ byStatus[status] += group.n;
276
+ }
277
+ else {
278
+ byStatus.pending += group.n;
255
279
  }
256
280
  }
281
+ // Utolsó módosítás: EGYETLEN doc (a legfrissebb), csak az időbélyeg-mezőre projektálva.
282
+ const newest = await collection
283
+ .find({}, { projection: { __lastModified: 1 } })
284
+ .sort({ __lastModified: -1 })
285
+ .limit(1)
286
+ .toArray();
287
+ const lastModifiedMs = newest[0]?.__lastModified ? new Date(newest[0].__lastModified).getTime() : 0;
257
288
  result.push({
258
289
  table: table,
259
- count: entries.length,
290
+ count: count,
260
291
  embeddingStatus: byStatus,
261
292
  lastModified: lastModifiedMs ? new Date(lastModifiedMs).toISOString() : null,
262
- status: entries.length ? 'ok' : 'empty',
293
+ status: count ? 'ok' : 'empty',
263
294
  });
264
295
  }
265
296
  return result;
@@ -434,14 +465,14 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
434
465
  if (!Api_Controller.HEAVY_WRITE_OPERATIONS.includes(operation)) {
435
466
  return;
436
467
  }
437
- const guard = fam_heap_guard_control_service_1.FAM_HeapGuard_ControlService.getInstance();
468
+ const guard = nts_dynamo_2.DyNTS_MemoryGuard.getInstance();
438
469
  if (!guard.shouldShedLoad()) {
439
470
  return;
440
471
  }
441
472
  const pressure = guard.pressure();
442
473
  throw new fsm_dynamo_1.DyFM_Error({
443
474
  errorCode: 'FAM-MEM-SHED-001',
444
- message: `Memória-nyomás (${pressure.usedMb}/${pressure.limitMb} MB, ${Math.round(pressure.ratio * 100)}%, `
475
+ message: `Memória-nyomás (${pressure.heapUsedMb}/${pressure.heapLimitMb} MB, ${pressure.heapPct}%, `
445
476
  + `GC ${Math.round(pressure.gcFraction * 100)}%) — a nehéz '${operation}' művelet ideiglenesen elutasítva `
446
477
  + '(load-shed). Próbáld újra, amikor a nyomás enyhül (lásd /api/logs), vagy szakaszold a scant (cooldown).',
447
478
  issuer: this.issuer,
@@ -163,7 +163,7 @@ class FAM_Config_ControlService {
163
163
  const dataService = new config_data_service_1.FAM_Config_DataService({ issuer: options.issuer ?? this.issuer });
164
164
  // Régi aktív rekord (ha van) → soft-delete (archív; history-megőrzés, dsgn-007 §7.2).
165
165
  const existing = await dataService.findActive(filter);
166
- if (existing && existing._id) {
166
+ if (existing?._id) {
167
167
  await dataService.deleteData(existing._id);
168
168
  }
169
169
  // Új aktív rekord — a `value` Mixed; a saveData $set-tel ír (Mongoose Mixed silent-drop ellen).
@@ -435,7 +435,7 @@ class FAM_Config_ControlService {
435
435
  async seedGlobalIfMissing(key, value) {
436
436
  const dataService = new config_data_service_1.FAM_Config_DataService({ issuer: this.issuer });
437
437
  const existing = await dataService.findActive({ level: fam_config_level_type_enum_1.FAM_ConfigLevel.global, key: key });
438
- if (existing && existing._id) {
438
+ if (existing?._id) {
439
439
  return;
440
440
  }
441
441
  await this.set(key, value, { level: fam_config_level_type_enum_1.FAM_ConfigLevel.global, setBy: 'env-seed', issuer: this.issuer });
@@ -12,8 +12,6 @@ const errors_controller_1 = require("./_routes/server/errors/errors.controller")
12
12
  const errors_control_service_1 = require("./_routes/server/errors/errors.control-service");
13
13
  const server_status_controller_1 = require("./_routes/server/server-status/server-status.controller");
14
14
  const embedding_1 = require("./_modules/embedding");
15
- const config_control_service_1 = require("./_routes/server/config/config.control-service");
16
- const fam_heap_guard_control_service_1 = require("./_collections/fam-heap-guard.control-service");
17
15
  const environment_1 = require("./environments/environment");
18
16
  const title_const_1 = require("./_collections/title.const");
19
17
  /**
@@ -54,6 +52,30 @@ class App extends nts_dynamo_1.DyNTS_App {
54
52
  // lokális) lekérdezhetővé teszi → az agent IS olvashatja REST-en (nem csak a tmp-fájlból). Install + enable.
55
53
  nts_dynamo_1.DyNTS_global_settings.log_settings.logs_endpoint = { enabled: true };
56
54
  logs_1.DyNTS_Logs_Service.getInstance().install();
55
+ // OOM-hibakezelés a BEDROCK `DyNTS_MemoryGuard`-ból (a base App auto-installálja a startup-ban). FAM-specifikus
56
+ // hangolás: a GC-thrash a fő OOM-jel (a FAM korpusza nagy, a heap-% csak ~85%-on ütött, de GC ~0.87-en thrash-elt),
57
+ // ezért a GC-fraction-küszöbök a kritikusak; a `%`-küszöböt magasra tesszük (csak genuine-danger). A critical
58
+ // nyomás load-shed-eli a nehéz write-eket (scan/import) az api-controllerben, + crash-handler (uncaughtException →
59
+ // teljes-részletű error-entry) ON. **`exitOnSustainedCritical` ON** (nts-dynamo 1.15.74 boot-grace-szel): a FAM
60
+ // boot-hidratálása (126k vektor) ELEVE GC-thrash-el (mért gc≈0.87), ezért `bootGraceMs: 120000` — a ~80s hidratálás
61
+ // + margó alatt a sustained-critical NEM lép ki (csak logol + load-shed); a grace UTÁN a tartós critical TISZTÁN
62
+ // kilép (exit 137) → a `fam serve` ps1-wrapper prediktíven újraindít (a kriptikus OOM exit-134 helyett). Az eredeti
63
+ // OOM uptime 771s-nél volt — jóval a grace után, tehát kezelve.
64
+ nts_dynamo_1.DyNTS_global_settings.memoryGuard = {
65
+ enabled: true,
66
+ pollIntervalMs: 3000,
67
+ heapWarningThreshold: 80,
68
+ heapCriticalThreshold: 90,
69
+ recoveryMargin: 10,
70
+ maxHistoryCount: 100,
71
+ gcWarningFraction: 0.40,
72
+ gcCriticalFraction: 0.60,
73
+ installCrashHandlers: true,
74
+ exitOnSustainedCritical: true,
75
+ sustainedCriticalPolls: 3,
76
+ exitCode: 137,
77
+ bootGraceMs: 120000,
78
+ };
57
79
  }
58
80
  getGlobalServiceCollection() {
59
81
  return {
@@ -119,34 +141,11 @@ class App extends nts_dynamo_1.DyNTS_App {
119
141
  * át, amit a write/scan path (MP-4/MP-5) hív mentés után. Lásd a `_modules/embedding` modul-doksit.
120
142
  */
121
143
  async postProcess() {
122
- // Heap-guard (OOM-hibakezelés) a DB/config UTÁN, a (memória-igényes) pool-hidratálás ELŐTT telepítve, hogy
123
- // a boot-növekedést is figyelje. A küszöbök a config-ból (memory.*), majd crash-handler + watchdog. Best-effort.
124
- await this.installHeapGuard();
144
+ // Az OOM-hibakezelést (DyNTS_MemoryGuard) a base App auto-installálja a startup-ban, a FAM-hangolással
145
+ // (overrideDynamoNTSGlobalSettings DyNTS_global_settings.memoryGuard) itt nincs külön teendő.
125
146
  await embedding_1.FAM_EmbeddingBootstrap_ControlService.getInstance().boot();
126
147
  fsm_dynamo_1.DyFM_Log.S_info(`UI is available at: localhost:${environment_1.env.httpPort}/api/app`);
127
148
  }
128
- /** A heap-guard küszöbeinek beolvasása a config-ból + telepítés. A config-hiba NEM buktatja a bootot (default-ok). */
129
- async installHeapGuard() {
130
- try {
131
- const config = config_control_service_1.FAM_Config_ControlService.getInstance();
132
- const num = async (key) => {
133
- const resolved = await config.resolve(key, {});
134
- return typeof resolved.value === 'number' ? resolved.value : undefined;
135
- };
136
- const enabledResolved = await config.resolve('memory.guardEnabled', {});
137
- fam_heap_guard_control_service_1.FAM_HeapGuard_ControlService.getInstance().configure({
138
- enabled: typeof enabledResolved.value === 'boolean' ? enabledResolved.value : undefined,
139
- warnRatio: await num('memory.warnRatio'),
140
- shedRatio: await num('memory.shedRatio'),
141
- exitRatio: await num('memory.exitRatio'),
142
- pollMs: await num('memory.pollMs'),
143
- });
144
- }
145
- catch (error) {
146
- fsm_dynamo_1.DyFM_Log.warn(`[heap-guard] config-olvasás sikertelen, default küszöbök: ${error?.message}`);
147
- }
148
- fam_heap_guard_control_service_1.FAM_HeapGuard_ControlService.getInstance().install();
149
- }
150
149
  getGlobalErrorHandler() {
151
150
  return async (err, req, res, issuer) => {
152
151
  // No-silent-failure (dsgn-008): minden hibát persistálunk a `fam_errors` tárba a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@futdevpro/fdp-agent-memory",
3
- "version": "1.1.21",
3
+ "version": "1.1.30",
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": {
@@ -55,7 +55,7 @@
55
55
  "license": "FDP",
56
56
  "dependencies": {
57
57
  "@futdevpro/fsm-dynamo": "^1.16.7",
58
- "@futdevpro/nts-dynamo": "^1.15.72",
58
+ "@futdevpro/nts-dynamo": "^1.15.74",
59
59
  "@modelcontextprotocol/sdk": "^1.12.0",
60
60
  "axios": "~1.8.1",
61
61
  "better-sqlite3": "^12.4.1",
@@ -1,176 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FAM_HeapGuard_ControlService = void 0;
4
- const tslib_1 = require("tslib");
5
- const v8 = tslib_1.__importStar(require("v8"));
6
- const perf_hooks_1 = require("perf_hooks");
7
- const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
8
- /**
9
- * `FAM_HeapGuard_ControlService` (user-FR 2026-06-21) — **OOM-hibakezelés**. A FAM-szerver V8-heapje a nagy korpuszon
10
- * (126k vektor) + nehéz műveletekkor (scan/import/re-embed) a plafonhoz érhet → „Ineffective mark-compacts near heap
11
- * limit" → FATAL exit 134 (nem catch-elhető). Ez a guard:
12
- *
13
- * 1. **Watchdog**: periodikusan méri a heap-arányt (`v8.getHeapStatistics`) ÉS a GC-hányadot (`perf_hooks` 'gc'
14
- * observer — a thrash valódi jele: a trace-ben `mu = 0.306`, azaz a futás 70%-a GC). A szint-váltásokat logolja
15
- * (a `/api/logs`-on az agent IS látja jönni a nyomást).
16
- * 2. **Load-shed**: `shouldShedLoad()` → a NEHÉZ write-ek (scan/import/re-embed) `critical` nyomásnál 503-mal
17
- * elutasítva, MIELŐTT egy újabb nagy allokáció átlöki a plafont (a könnyű read/create/update fut tovább).
18
- * 3. **Graceful exit**: ha a nyomás a `exitRatio` fölött tartós (GC-vel sem csökken), strukturált diagnosztika +
19
- * tiszta `process.exit` → a `fam serve` ps1-wrapper PREDIKTÍVAN újraindít (a kriptikus exit-134 helyett).
20
- * 4. **Crash-handler**: `uncaughtException` / `unhandledRejection` → strukturált log + tiszta exit (eddig néma volt).
21
- *
22
- * Lokális, single-user (ADR-004) → a load-shed a saját gépet védi, nem multi-tenant fairness. A küszöbök
23
- * config-/env-kapcsolósak; a tiszta szint-számítás (`computeLevel`) pure + külön tesztelt.
24
- */
25
- class FAM_HeapGuard_ControlService {
26
- static _instance;
27
- /** GC-hányad-küszöb a `warn`/`critical` szinthez (a thrash-jelhez; a ratio MELLETT). */
28
- static GC_WARN_FRACTION = 0.40;
29
- static GC_CRITICAL_FRACTION = 0.60;
30
- thresholds = {
31
- warnRatio: 0.70, shedRatio: 0.82, exitRatio: 0.92, pollMs: 3000, enabled: true,
32
- };
33
- installed = false;
34
- timer;
35
- gcObserver;
36
- lastLevel = 'ok';
37
- /** A GC-ben töltött ms gördülő ablaka (timestamp + duration párok az utolsó ~pollMs-re). */
38
- gcEvents = [];
39
- static getInstance() {
40
- if (!FAM_HeapGuard_ControlService._instance) {
41
- FAM_HeapGuard_ControlService._instance = new FAM_HeapGuard_ControlService();
42
- }
43
- return FAM_HeapGuard_ControlService._instance;
44
- }
45
- /** A küszöbök beállítása (install ELŐTT) — clamp `[0..1]` (a pollMs `[500..60000]`). Hiányzó mező → marad a default. */
46
- configure(partial) {
47
- const clampRatio = (value, fallback) => (typeof value === 'number' && value > 0 && value <= 1) ? value : fallback;
48
- this.thresholds = {
49
- warnRatio: clampRatio(partial.warnRatio, this.thresholds.warnRatio),
50
- shedRatio: clampRatio(partial.shedRatio, this.thresholds.shedRatio),
51
- exitRatio: clampRatio(partial.exitRatio, this.thresholds.exitRatio),
52
- pollMs: typeof partial.pollMs === 'number' && partial.pollMs >= 500
53
- ? Math.min(partial.pollMs, 60000) : this.thresholds.pollMs,
54
- enabled: typeof partial.enabled === 'boolean' ? partial.enabled : this.thresholds.enabled,
55
- };
56
- }
57
- /** A crash-handlerek + (ha enabled) a watchdog + GC-observer telepítése. Idempotens (egyszer fut). Best-effort. */
58
- install() {
59
- if (this.installed) {
60
- return;
61
- }
62
- this.installed = true;
63
- process.on('uncaughtException', (error) => this.onFatal('uncaughtException', error));
64
- process.on('unhandledRejection', (reason) => this.onFatal('unhandledRejection', reason));
65
- if (!this.thresholds.enabled) {
66
- fsm_dynamo_1.DyFM_Log.info('[heap-guard] disabled (memory.guardEnabled=false) — csak a crash-handlerek aktívak.');
67
- return;
68
- }
69
- this.installGcObserver();
70
- this.timer = setInterval(() => this.tick(), this.thresholds.pollMs);
71
- // A timer NE tartsa életben a process-t (a graceful shutdownt ne blokkolja).
72
- this.timer.unref?.();
73
- fsm_dynamo_1.DyFM_Log.info(`[heap-guard] aktív (poll ${this.thresholds.pollMs}ms; shed@${Math.round(this.thresholds.shedRatio * 100)}%, `
74
- + `exit@${Math.round(this.thresholds.exitRatio * 100)}%).`);
75
- }
76
- /** Pure szint-számítás (külön tesztelhető): a ratio ÉS a GC-hányad közül a ROSSZABB dönt. */
77
- static computeLevel(ratio, gcFraction, thresholds) {
78
- if (ratio >= thresholds.shedRatio || gcFraction >= FAM_HeapGuard_ControlService.GC_CRITICAL_FRACTION) {
79
- return 'critical';
80
- }
81
- if (ratio >= thresholds.warnRatio || gcFraction >= FAM_HeapGuard_ControlService.GC_WARN_FRACTION) {
82
- return 'warn';
83
- }
84
- return 'ok';
85
- }
86
- /** A pillanatnyi heap-nyomás (ratio + GC-hányad → szint). Observability + load-shed forrása. */
87
- pressure() {
88
- const stats = v8.getHeapStatistics();
89
- const usedMb = Math.round(stats.used_heap_size / 1048576);
90
- const limitMb = Math.round(stats.heap_size_limit / 1048576);
91
- const ratio = stats.heap_size_limit ? stats.used_heap_size / stats.heap_size_limit : 0;
92
- const gcFraction = this.recentGcFraction();
93
- const level = FAM_HeapGuard_ControlService.computeLevel(ratio, gcFraction, this.thresholds);
94
- return { level: level, usedMb: usedMb, limitMb: limitMb, ratio: ratio, gcFraction: gcFraction };
95
- }
96
- /** `true` → a NEHÉZ write-eket (scan/import/re-embed) le kell shed-elni (critical nyomás). Disabled → soha. */
97
- shouldShedLoad() {
98
- return this.thresholds.enabled && this.pressure().level === 'critical';
99
- }
100
- /** A GC-hányad az utolsó `pollMs` ablakban (GC-ms / ablak-ms; clamp [0..1]). */
101
- recentGcFraction() {
102
- const windowMs = this.thresholds.pollMs;
103
- const cutoff = Date.now() - windowMs;
104
- this.gcEvents = this.gcEvents.filter((event) => event.at >= cutoff);
105
- const gcMs = this.gcEvents.reduce((sum, event) => sum + event.ms, 0);
106
- return Math.min(gcMs / windowMs, 1);
107
- }
108
- /** A 'gc' perf-entry-k figyelése — a GC-ben töltött időt gyűjti a gördülő ablakhoz. */
109
- installGcObserver() {
110
- try {
111
- this.gcObserver = new perf_hooks_1.PerformanceObserver((list) => {
112
- const now = Date.now();
113
- for (const entry of list.getEntries()) {
114
- this.gcEvents.push({ at: now, ms: entry.duration });
115
- }
116
- });
117
- this.gcObserver.observe({ entryTypes: ['gc'] });
118
- }
119
- catch {
120
- // A GC-observer nem kritikus (a ratio-jel marad) — best-effort.
121
- }
122
- }
123
- /** A watchdog-tick: szint-váltás logolása + a tartós veszély-zóna graceful-exit-je. */
124
- tick() {
125
- const pressure = this.pressure();
126
- if (pressure.level !== this.lastLevel) {
127
- const line = `[heap-guard] ${this.lastLevel}→${pressure.level} `
128
- + `(${pressure.usedMb}/${pressure.limitMb} MB ${Math.round(pressure.ratio * 100)}%, GC ${Math.round(pressure.gcFraction * 100)}%)`;
129
- if (pressure.level === 'critical') {
130
- fsm_dynamo_1.DyFM_Log.warn(`${line} — a nehéz write-ek (scan/import/re-embed) load-shed (503).`);
131
- }
132
- else if (pressure.level === 'warn') {
133
- fsm_dynamo_1.DyFM_Log.warn(line);
134
- }
135
- else {
136
- fsm_dynamo_1.DyFM_Log.info(`${line} — nyomás enyhült.`);
137
- }
138
- this.lastLevel = pressure.level;
139
- }
140
- // Tartós veszély-zóna: GC-kísérlet, majd ha nem csökken → tiszta, logolt exit (a ps1 újraindít).
141
- if (pressure.ratio >= this.thresholds.exitRatio) {
142
- this.attemptGcReclaim();
143
- const after = this.pressure();
144
- if (after.ratio >= this.thresholds.exitRatio) {
145
- fsm_dynamo_1.DyFM_Log.error(`[heap-guard] HEAP-VÉSZ ${after.usedMb}/${after.limitMb} MB (${Math.round(after.ratio * 100)}%) — `
146
- + 'graceful exit, hogy a `fam serve` TISZTÁN újraindítson (a kriptikus OOM exit-134 elkerülése).');
147
- // Egy tick haladékot adunk a log flush-nek, majd restartolható kóddal kilépünk.
148
- setTimeout(() => process.exit(137), 250).unref?.();
149
- }
150
- }
151
- }
152
- /** Best-effort GC-kényszerítés (csak ha `--expose-gc`). A near-limit utolsó esély a tiszta-exit ELŐTT. */
153
- attemptGcReclaim() {
154
- try {
155
- const maybeGc = global.gc;
156
- if (maybeGc) {
157
- maybeGc();
158
- }
159
- }
160
- catch {
161
- // nincs --expose-gc → nincs mit tenni; a tiszta-exit a fallback.
162
- }
163
- }
164
- /** Strukturált crash-log + tiszta exit (a `fam serve` ps1 újraindít). Az OOM-fatal NEM ide jön (nem catch-elhető). */
165
- onFatal(kind, error) {
166
- try {
167
- const message = error instanceof Error ? `${error.message}\n${error.stack ?? ''}` : String(error);
168
- fsm_dynamo_1.DyFM_Log.error(`[heap-guard] FATAL ${kind}: ${message}`);
169
- }
170
- catch {
171
- // a log se buktasson — best-effort.
172
- }
173
- setTimeout(() => process.exit(1), 250).unref?.();
174
- }
175
- }
176
- exports.FAM_HeapGuard_ControlService = FAM_HeapGuard_ControlService;