@futdevpro/fdp-agent-memory 1.1.12 → 1.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/package.json +1 -1
- package/build/src/_cli/_commands/config.command.js +4 -1
- package/build/src/_cli/_commands/scan-projects.command.js +26 -1
- package/build/src/_cli/_commands/serve.command.js +28 -5
- package/build/src/_collections/config-catalog.const.js +56 -0
- package/build/src/_collections/fam-config-value-coerce.util.js +63 -0
- package/build/src/_collections/fam-cooldown.util.js +35 -0
- package/build/src/_collections/fam-heap-guard.control-service.js +176 -0
- package/build/src/_collections/title.const.js +31 -0
- package/build/src/_modules/ingest/_collections/fam-famignore.util.js +57 -1
- package/build/src/_modules/ingest/_collections/fam-git-repo.util.js +193 -0
- package/build/src/_modules/ingest/_collections/fam-scan-reconcile.util.js +40 -0
- package/build/src/_modules/ingest/_collections/fam-secret-content.util.js +49 -0
- package/build/src/_modules/ingest/_collections/fam-secret-exclude.util.js +12 -0
- package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +38 -8
- package/build/src/_modules/ingest/_services/fam-scan.control-service.js +77 -11
- package/build/src/_routes/server/api/api.controller.js +38 -2
- package/build/src/app.server.js +31 -1
- package/package.json +1 -1
package/build/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@futdevpro/fdp-agent-memory",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.21",
|
|
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": {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FAM_Config_Util = void 0;
|
|
4
4
|
const commander_1 = require("commander");
|
|
5
|
+
const fam_config_value_coerce_util_1 = require("../../_collections/fam-config-value-coerce.util");
|
|
5
6
|
const fam_output_util_1 = require("../_collections/fam-output.util");
|
|
6
7
|
const fam_client_service_1 = require("../_services/fam-client.service");
|
|
7
8
|
const fam_cli_const_1 = require("../_collections/fam-cli.const");
|
|
@@ -67,9 +68,11 @@ class FAM_Config_Util {
|
|
|
67
68
|
static async runSet(command) {
|
|
68
69
|
const globals = command.optsWithGlobals();
|
|
69
70
|
const local = command.opts();
|
|
71
|
+
// A CLI `--value` mindig string; a szerver típus-szigorúan validál → a katalógus-típusra konvertáljuk
|
|
72
|
+
// (boolean/number/string[] kulcsok így CLI-ből is állíthatók — FAM_ConfigValueCoerce_Util).
|
|
70
73
|
const body = {
|
|
71
74
|
key: local.key,
|
|
72
|
-
value: local.value,
|
|
75
|
+
value: fam_config_value_coerce_util_1.FAM_ConfigValueCoerce_Util.coerce(local.key, local.value),
|
|
73
76
|
table: local.table,
|
|
74
77
|
scopeId: local.scope,
|
|
75
78
|
};
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.FAM_ScanProjects_Util = void 0;
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
5
|
const commander_1 = require("commander");
|
|
6
|
+
const fam_cooldown_util_1 = require("../../_collections/fam-cooldown.util");
|
|
6
7
|
const fam_output_util_1 = require("../_collections/fam-output.util");
|
|
7
8
|
const fam_client_service_1 = require("../_services/fam-client.service");
|
|
8
9
|
const fam_cli_const_1 = require("../_collections/fam-cli.const");
|
|
@@ -26,6 +27,8 @@ class FAM_ScanProjects_Util {
|
|
|
26
27
|
.option('--org <name>', 'default organization az auto-felderítéshez', 'FutDevPro')
|
|
27
28
|
.option('--table <documents|codebase>', 'default cél-tár', 'codebase')
|
|
28
29
|
.option('--dry-run', 'csak becslés MINDEN projektre, NEM ír', false)
|
|
30
|
+
.option('--cooldown <ms>', 'cooldown a projektek KÖZÉ (ms) — gép-tehermentesítő szünet nagy multi-scanhez; '
|
|
31
|
+
+ 'felülírja a `scan.projectCooldownMs` configot (default: a config, ami 0 = ki)')
|
|
29
32
|
.option('--stop-on-error', 'az első hibánál leáll (default: folytatja a többivel)', false)
|
|
30
33
|
.addHelpText('before', '\n⚠️ TISZTA MULTI-SCAN — ELŐBB minden projektbe IGNORE-FÁJL:\n'
|
|
31
34
|
+ ' Mivel ez TÖBB projektet szkennel egyszerre, a junk gyorsan elszaporodik. MINDEN érintett\n'
|
|
@@ -77,7 +80,11 @@ class FAM_ScanProjects_Util {
|
|
|
77
80
|
const client = new fam_client_service_1.FAM_CliClient_Service(globals.serverUrl);
|
|
78
81
|
const projects = [];
|
|
79
82
|
let okCount = 0;
|
|
80
|
-
|
|
83
|
+
// Projekt-cooldown (user-FR): a `--cooldown <ms>` felülírja a szerver `scan.projectCooldownMs` configját
|
|
84
|
+
// (default 0 = ki). dryRun-on nincs cooldown (nincs valódi embed-munka). A projektek KÖZÉ vár, nem az utolsó után.
|
|
85
|
+
const projectCooldownMs = local.dryRun ? 0 : await FAM_ScanProjects_Util.resolveCooldownMs(client, local.cooldown);
|
|
86
|
+
FAM_ScanProjects_Util.progress(globals, `=== ${specs.length} projekt SOROS scan-je${local.dryRun ? ' (dryRun)' : ''}`
|
|
87
|
+
+ `${projectCooldownMs > 0 ? `, cooldown ${projectCooldownMs}ms/projekt` : ''} ===`);
|
|
81
88
|
for (let i = 0; i < specs.length; i++) {
|
|
82
89
|
const spec = specs[i];
|
|
83
90
|
FAM_ScanProjects_Util.progress(globals, `[${i + 1}/${specs.length}] ${spec.project} (${spec.path})`);
|
|
@@ -109,6 +116,11 @@ class FAM_ScanProjects_Util {
|
|
|
109
116
|
break;
|
|
110
117
|
}
|
|
111
118
|
}
|
|
119
|
+
// Projekt-cooldown: a projektek KÖZÉ (NEM az utolsó után) — gép-tehermentesítő szünet nagy multi-scanhez.
|
|
120
|
+
if (projectCooldownMs > 0 && i < specs.length - 1) {
|
|
121
|
+
FAM_ScanProjects_Util.progress(globals, ` cooldown ${projectCooldownMs}ms…`);
|
|
122
|
+
await fam_cooldown_util_1.FAM_Cooldown_Util.sleep(projectCooldownMs);
|
|
123
|
+
}
|
|
112
124
|
}
|
|
113
125
|
FAM_ScanProjects_Util.progress(globals, `=== KÉSZ: ${okCount}/${specs.length} ok ===`);
|
|
114
126
|
return fam_output_util_1.FAM_Output_Util.success({
|
|
@@ -116,6 +128,19 @@ class FAM_ScanProjects_Util {
|
|
|
116
128
|
data: { total: specs.length, ok: okCount, projects: projects },
|
|
117
129
|
});
|
|
118
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* A projekt-cooldown ms feloldása: a CLI `--cooldown` felülírja a szerver `scan.projectCooldownMs` configját.
|
|
133
|
+
* Nincs CLI-érték → a szerver effektív configját kérdezi (`GET /api/config`); a config-lekérés hibája → 0 (a
|
|
134
|
+
* cooldown sosem buktatja a scant). Minden út a `FAM_Cooldown_Util.resolveMs`-en megy át (clamp + biztonság).
|
|
135
|
+
*/
|
|
136
|
+
static async resolveCooldownMs(client, cliValue) {
|
|
137
|
+
if (cliValue !== undefined) {
|
|
138
|
+
return fam_cooldown_util_1.FAM_Cooldown_Util.resolveMs(cliValue);
|
|
139
|
+
}
|
|
140
|
+
const result = await client.get('/api/config');
|
|
141
|
+
const value = result.ok ? result.data?.effective?.['scan.projectCooldownMs'] : 0;
|
|
142
|
+
return fam_cooldown_util_1.FAM_Cooldown_Util.resolveMs(value);
|
|
143
|
+
}
|
|
119
144
|
/** A scan-request body egy projekt-spec-hez (dryRun vagy valós). */
|
|
120
145
|
static body(spec, dryRun, defaults) {
|
|
121
146
|
return {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FAM_Serve_Util = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
4
5
|
const child_process_1 = require("child_process");
|
|
6
|
+
const net = tslib_1.__importStar(require("net"));
|
|
5
7
|
const fs_1 = require("fs");
|
|
6
8
|
const os_1 = require("os");
|
|
7
9
|
const path_1 = require("path");
|
|
@@ -147,7 +149,13 @@ class FAM_Serve_Util {
|
|
|
147
149
|
if (action === 'restart') {
|
|
148
150
|
fam_output_util_1.FAM_Output_Util.logInfo(globals, `FAM szerver RESTART: a(z) ${port} porton futó szerver leállítása…`);
|
|
149
151
|
await FAM_Serve_Util.stopServerOnPort(port);
|
|
150
|
-
|
|
152
|
+
// A health-down NEM elég: az új szerver bind-preflight-je a TÉNYLEGESEN SZABAD (bindolható) portot
|
|
153
|
+
// igényli (a kill utáni process-halál + TIME_WAIT lassú lehet) → a port bindolhatóságát várjuk.
|
|
154
|
+
const freed = await FAM_Serve_Util.waitForPortFree(port, 12000);
|
|
155
|
+
if (!freed) {
|
|
156
|
+
fam_output_util_1.FAM_Output_Util.logInfo(globals, `⚠ a(z) ${port} port 12s alatt nem szabadult fel — az indítás megpróbálom, `
|
|
157
|
+
+ 'de ha bind-hiba van, futtasd újra a `fam serve --restart`-ot.');
|
|
158
|
+
}
|
|
151
159
|
}
|
|
152
160
|
return FAM_Serve_Util.spawnServer({ globals: globals, local: local, port: port, healthUrl: healthUrl, client: client });
|
|
153
161
|
}
|
|
@@ -265,14 +273,24 @@ class FAM_Serve_Util {
|
|
|
265
273
|
return false;
|
|
266
274
|
}
|
|
267
275
|
/** A port LE-állásának bevárása (a `--restart` kill után, mielőtt újraindítunk), felső korláttal. */
|
|
268
|
-
static async
|
|
276
|
+
static async waitForPortFree(port, timeoutMs) {
|
|
269
277
|
const deadline = Date.now() + timeoutMs;
|
|
270
278
|
while (Date.now() < deadline) {
|
|
271
|
-
if (
|
|
272
|
-
return;
|
|
279
|
+
if (await FAM_Serve_Util.isPortFree(port)) {
|
|
280
|
+
return true;
|
|
273
281
|
}
|
|
274
282
|
await FAM_Serve_Util.sleep(FAM_Serve_Util.HEALTH_POLL_MS);
|
|
275
283
|
}
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
/** Bindolható-e (szabad-e) a port? Egy próba-`listen` a loopbackon, azonnali close — `true`, ha köthető. */
|
|
287
|
+
static isPortFree(port) {
|
|
288
|
+
return new Promise((resolveCheck) => {
|
|
289
|
+
const tester = net.createServer();
|
|
290
|
+
tester.once('error', () => resolveCheck(false));
|
|
291
|
+
tester.once('listening', () => tester.close(() => resolveCheck(true)));
|
|
292
|
+
tester.listen(port, '127.0.0.1');
|
|
293
|
+
});
|
|
276
294
|
}
|
|
277
295
|
/**
|
|
278
296
|
* A megadott porton FIGYELŐ process leállítása (a `--restart` magja). Platform-natív, fail-safe: Windows →
|
|
@@ -284,7 +302,12 @@ class FAM_Serve_Util {
|
|
|
284
302
|
const command = isWin ? (process.env.ComSpec ?? 'powershell.exe') : 'sh';
|
|
285
303
|
const psScript = `$ErrorActionPreference='SilentlyContinue';`
|
|
286
304
|
+ `(Get-NetTCPConnection -LocalPort ${port} -State Listen).OwningProcess | Select-Object -Unique | `
|
|
287
|
-
+ `ForEach-Object { Stop-Process -Id $_ -Force }
|
|
305
|
+
+ `ForEach-Object { Stop-Process -Id $_ -Force };`
|
|
306
|
+
// A serve-ablakot (a fam-server-<port>.ps1-t futtató powershell) is leállítjuk → nincs árva,
|
|
307
|
+
// „[FAM szerver kilépett]" zombi-ablak a restart után (különben halmozódnak).
|
|
308
|
+
+ `Get-CimInstance Win32_Process -Filter "Name='powershell.exe'" | `
|
|
309
|
+
+ `Where-Object { $_.CommandLine -like '*fam-server-${port}*' } | `
|
|
310
|
+
+ `ForEach-Object { Stop-Process -Id $_.ProcessId -Force }`;
|
|
288
311
|
const args = isWin
|
|
289
312
|
? ['-NoProfile', '-Command', psScript]
|
|
290
313
|
: ['-c', `lsof -ti tcp:${port} | xargs -r kill -9`];
|
|
@@ -205,12 +205,42 @@ exports.CONFIG_CATALOG = {
|
|
|
205
205
|
type: 'boolean', default: false, levels: ALL_LEVELS,
|
|
206
206
|
description: 'Symlink-követés (path-traversal védelem, dsgn-004).',
|
|
207
207
|
},
|
|
208
|
+
'scan.nestedFamignore': {
|
|
209
|
+
type: 'boolean', default: true, levels: ALL_LEVELS,
|
|
210
|
+
description: 'A NESTED (per-alkönyvtár) `.fdpfamignore`-ok figyelembevétele a bejárás közben. `true` (default) '
|
|
211
|
+
+ '= ha egy al-projektben/al-mappában van saját `.fdpfamignore`, azt is betöltjük (a saját al-útjához '
|
|
212
|
+
+ 'horgonyozva) és alkalmazzuk arra a subtree-re — így a teljes-workspace scan tiszteletben tartja az '
|
|
213
|
+
+ 'al-projektek saját ignore-jait (eleve-tisztán-szkennelés). `false` = csak a scan-GYÖKÉR `.fdpfamignore`-ja '
|
|
214
|
+
+ 'érvényes (a régi, v1-viselkedés — ha valaki kizárólag gyökér-szintű kontrollt akar).',
|
|
215
|
+
},
|
|
216
|
+
'scan.reconcileIgnored': {
|
|
217
|
+
type: 'boolean', default: true, levels: ALL_LEVELS,
|
|
218
|
+
description: 'Re-scankor a MOST ignore-minta által kizárt (de korábban beolvasott) fájlok meglévő chunkjainak '
|
|
219
|
+
+ 'kitakarítása a tárból. `true` (default — TISZTOGATÁS) = ha utólag hozzáadsz egy ignore-/famignore-mintát, '
|
|
220
|
+
+ 'a re-scan az így kizárttá vált fájlok régi bejegyzéseit orphanként soft-delete-eli (nem ragad be, amit '
|
|
221
|
+
+ 'ignore-olni akarsz). `false` = a régi viselkedés (az ignore-skippelt fájl „jelen-lévőként" számít, a régi '
|
|
222
|
+
+ 'chunkjai BENT MARADNAK — ha valaki a korábbi beolvasást meg akarja őrizni). A lemezről ELTŰNT fájlokat '
|
|
223
|
+
+ 'mindkét beállítás reconciliálja (ez attól független).',
|
|
224
|
+
},
|
|
208
225
|
'scan.testFileWeight': {
|
|
209
226
|
type: 'number', default: 0.5, min: 0.0, max: 10.0, levels: ALL_LEVELS,
|
|
210
227
|
description: 'A teszt-/spec-fájlok (`*.spec.*` / `*.test.*`) DEFAULT retrieval-súlya (FAM-REV-058) — a próza-'
|
|
211
228
|
+ 'nehéz specek különben az IMPLEMENTÁCIÓ fölé rangsorolnak. <1 → a forrás alá; explicit scan-`weights` '
|
|
212
229
|
+ 'felülírja; 1 = nincs csökkentés.',
|
|
213
230
|
},
|
|
231
|
+
'scan.sectionCooldownMs': {
|
|
232
|
+
type: 'number', default: 0, min: 0, max: 60000, integer: true, levels: ALL_LEVELS,
|
|
233
|
+
description: 'Kikapcsolható cooldown (ms) a SZEKCIÓK (file-batch-ek) KÖZÉ egy scanen belül — a NAGY DB-k '
|
|
234
|
+
+ 'beolvasásakor ad lélegzetvételi szünetet a gépnek (CPU/embed-provider/Mongo ne terhelődjön túl). '
|
|
235
|
+
+ 'Default `0` = KIKAPCSOLVA (nincs lassítás); nagy scanre állítsd pl. 1000–3000-re. Csak a batch-ek '
|
|
236
|
+
+ 'KÖZÖTT vár (az utolsó után nem), és a `dryRun` NEM vár (nincs valódi embed-munka).',
|
|
237
|
+
},
|
|
238
|
+
'scan.projectCooldownMs': {
|
|
239
|
+
type: 'number', default: 0, min: 0, max: 60000, integer: true, levels: ALL_LEVELS,
|
|
240
|
+
description: 'Kikapcsolható cooldown (ms) a PROJEKTEK KÖZÉ a `scan-projects` soros multi-scan-jében — a sok-'
|
|
241
|
+
+ 'projektes (L2/L6) beolvasás ne thrash-elje a gépet. Default `0` = KIKAPCSOLVA; nagy futásra pl. 2000–'
|
|
242
|
+
+ '5000. A CLI `--cooldown <ms>` felülírja ezt a config-default-ot (a projektek KÖZÖTT vár, az utolsó után nem).',
|
|
243
|
+
},
|
|
214
244
|
'scan.estimateMsPerChunk': {
|
|
215
245
|
type: 'number', default: 250, min: 1, max: 10000, integer: true, levels: ALL_LEVELS,
|
|
216
246
|
description: 'A `dryRun` pre-scan idő-becsléshez: becsült embed-idő chunkonként (ms). DURVA, gép-/modell-/'
|
|
@@ -239,6 +269,32 @@ exports.CONFIG_CATALOG = {
|
|
|
239
269
|
type: 'number', default: 20, min: 1, max: 200, integer: true, levels: GLOBAL_TABLE,
|
|
240
270
|
description: 'Párhuzamos ingest flow-limit (CCAP DEFAULT_INGEST_FLOW_LIMIT=20, cap 200).',
|
|
241
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
|
+
},
|
|
242
298
|
'patterns.defaultWeight': {
|
|
243
299
|
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,
|
|
244
300
|
description: 'A coding_patterns tár default retrieval-súlya (CCAP weight 5; csak coding_patterns).',
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_ConfigValueCoerce_Util = void 0;
|
|
4
|
+
const config_catalog_const_1 = require("./config-catalog.const");
|
|
5
|
+
/**
|
|
6
|
+
* `FAM_ConfigValueCoerce_Util` — a `config set` CLI nyers `--value` STRING-jét a katalógus-kulcs típusára konvertálja
|
|
7
|
+
* (a CLI argumentum mindig string, a szerver viszont típus-szigorúan validál — boolean/number kulcsot string-ként
|
|
8
|
+
* elutasít: `FAM-CONFIG-SET-002`). Ez teszi a config-kapcsolókat (pl. `scan.nestedFamignore` / `scan.reconcileIgnored`
|
|
9
|
+
* / `scan.followSymlinks`) ténylegesen CLI-ből állíthatóvá. A `CONFIG_CATALOG[key].type` alapján:
|
|
10
|
+
*
|
|
11
|
+
* - `boolean` → `'true'`/`'false'` → bool (más → változatlan string, hogy a szerver adjon egyértelmű hibát);
|
|
12
|
+
* - `number` → `Number(raw)` (NaN → változatlan string);
|
|
13
|
+
* - `number-or-null` → `'null'`/üres → `null`, különben `Number(raw)`;
|
|
14
|
+
* - `string[]` → JSON-tömb (`[...]`) VAGY vesszős lista → `string[]`;
|
|
15
|
+
* - `string` (+ enum) → változatlan.
|
|
16
|
+
*
|
|
17
|
+
* Ismeretlen kulcs → változatlan string (a szerver `FAM-CONFIG-SET-001`-gyel utasítja el). Tisztán pure.
|
|
18
|
+
*/
|
|
19
|
+
class FAM_ConfigValueCoerce_Util {
|
|
20
|
+
/** A nyers CLI-string → a `key` katalógus-típusának megfelelő JS-érték (lásd az osztály-leírást). */
|
|
21
|
+
static coerce(key, raw) {
|
|
22
|
+
const entry = config_catalog_const_1.CONFIG_CATALOG[key];
|
|
23
|
+
if (!entry) {
|
|
24
|
+
return raw;
|
|
25
|
+
}
|
|
26
|
+
switch (entry.type) {
|
|
27
|
+
case 'boolean':
|
|
28
|
+
if (raw === 'true') {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
if (raw === 'false') {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return raw;
|
|
35
|
+
case 'number': {
|
|
36
|
+
const value = Number(raw);
|
|
37
|
+
return Number.isNaN(value) ? raw : value;
|
|
38
|
+
}
|
|
39
|
+
case 'number-or-null': {
|
|
40
|
+
if (raw === 'null' || raw === '') {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const value = Number(raw);
|
|
44
|
+
return Number.isNaN(value) ? raw : value;
|
|
45
|
+
}
|
|
46
|
+
case 'string[]': {
|
|
47
|
+
const trimmed = raw.trim();
|
|
48
|
+
if (trimmed.startsWith('[')) {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(trimmed);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// nem valid JSON → essünk át a vesszős listára
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return trimmed.length ? trimmed.split(',').map((item) => item.trim()).filter(Boolean) : [];
|
|
57
|
+
}
|
|
58
|
+
default:
|
|
59
|
+
return raw;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.FAM_ConfigValueCoerce_Util = FAM_ConfigValueCoerce_Util;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_Cooldown_Util = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* `FAM_Cooldown_Util` (user-FR 2026-06-21) — opcionális, **kikapcsolható** cooldown (lélegzetvételi szünet) a
|
|
6
|
+
* scan-feldolgozás közé, hogy a NAGY adatbázisok beolvasása NE terhelje túl a gépet (CPU/embed-provider/Mongo).
|
|
7
|
+
* Két ponton fogyasztva: (a) a SZEKCIÓK (file-batch-ek) között egy scanen belül (`scan.sectionCooldownMs`, szerver-
|
|
8
|
+
* oldali ingest-loop); (b) a PROJEKTEK között a `scan-projects` soros orchestrátorban (`scan.projectCooldownMs` /
|
|
9
|
+
* CLI `--cooldown`). Default **0 = kikapcsolva** (nincs lassítás) → opt-in: a user nagy scanre bekapcsolja.
|
|
10
|
+
*
|
|
11
|
+
* Tisztán: a `resolveMs` (parse + clamp) pure + külön tesztelhető; a `sleep` az egyetlen side-effect.
|
|
12
|
+
*/
|
|
13
|
+
class FAM_Cooldown_Util {
|
|
14
|
+
/** Biztonsági felső korlát egy cooldown-ra (ms) — egy elgépelt óriás-érték ne fagyassza be a scant. */
|
|
15
|
+
static MAX_MS = 60000;
|
|
16
|
+
/**
|
|
17
|
+
* Nyers config/CLI érték → biztonságos cooldown ms. Nem-szám / NaN / ≤0 → `0` (kikapcsolva); egyébként egész
|
|
18
|
+
* ms-re vágva, `[0, MAX_MS]`-re clamp-elve. Így a hibás/extrém input sosem buktatja vagy fagyasztja a scant.
|
|
19
|
+
*/
|
|
20
|
+
static resolveMs(raw) {
|
|
21
|
+
const value = typeof raw === 'number' ? raw : Number(raw);
|
|
22
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
return Math.min(Math.floor(value), FAM_Cooldown_Util.MAX_MS);
|
|
26
|
+
}
|
|
27
|
+
/** Vár `ms` ideig (≤0 → azonnal visszatér, nincs timer). A cooldown egyetlen tényleges késleltetése. */
|
|
28
|
+
static async sleep(ms) {
|
|
29
|
+
if (ms <= 0) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.FAM_Cooldown_Util = FAM_Cooldown_Util;
|
|
@@ -0,0 +1,176 @@
|
|
|
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;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_title = void 0;
|
|
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
|
+
|
|
31
|
+
FDP Agent Memory - By Future Development Program Kft.`;
|
|
@@ -4,6 +4,7 @@ exports.FAM_FamIgnore_Util = void 0;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const fs = tslib_1.__importStar(require("fs"));
|
|
6
6
|
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
const fam_glob_match_util_1 = require("./fam-glob-match.util");
|
|
7
8
|
/**
|
|
8
9
|
* `FAM_FamIgnore_Util` (descriptive user feedback / nagy-scan ergonómia) — a **`.fdpfamignore`** in-repo,
|
|
9
10
|
* gitignore-stílusú ignore-fájl támogatása. A projekt (vagy a workspace-gyökér) MAGÁBAN hordozza, mit hagyjon
|
|
@@ -13,7 +14,10 @@ const path = tslib_1.__importStar(require("path"));
|
|
|
13
14
|
* **Szintaxis (gitignore-szerű, a `FAM_GlobMatch_Util`-ra fordítva):** soronként egy minta; `#` komment + üres
|
|
14
15
|
* sor kihagyva. `node_modules` (csupasz név) → bárhol illeszkedik; `/build` (vezető `/`) → a gyökérhez horgonyzott;
|
|
15
16
|
* `dist/` (záró `/`) → könyvtár; `src/gen` (beágyazott `/`) → gyökér-relatív; `*.tgz` → bárhol. **A `!` negáció
|
|
16
|
-
*
|
|
17
|
+
* (re-include) TÁMOGATOTT** (user-FR 2026-06-21): `parse` az ignore-sorokat, `parseNegations` a `!`-sorokat adja;
|
|
18
|
+
* a fogyasztó (scan) a fájlt akkor SKIP-eli, ha ignore-match ÉS NEM re-include-match — a könyvtár-prune pedig
|
|
19
|
+
* `dirOnReincludeLineage`-tudatos (egy ignore-olt mappát NEM vág le, ha re-included elem van rajta/alatta). Így
|
|
20
|
+
* a „ignore-mindent-kivéve-X" (pl. `ccap/` + `!ccap/client/`) helyesen működik a rekurzív bejárásban is.
|
|
17
21
|
*
|
|
18
22
|
* **v1 hatókör:** a scan-GYÖKÉR `.fdpfamignore`-ja (a teljes scanre). A NESTED (per-alkönyvtár) `.fdpfamignore`
|
|
19
23
|
* a két-fázisú felderítés (walk → classify) miatt külön kör — BACKLOG (a `baseDir` paraméter már készen áll rá).
|
|
@@ -62,9 +66,61 @@ class FAM_FamIgnore_Util {
|
|
|
62
66
|
}
|
|
63
67
|
return patterns;
|
|
64
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* A `!`-negáció (**re-include**) sorai — a vezető `!` levágva. Tisztán pure (a fordítás külön). Az `ignore`
|
|
71
|
+
* (`parse`) + a re-include (`parseNegations`) együtt adja a gitignore-szerű „ignore-mindent-kivéve" szemantikát.
|
|
72
|
+
*/
|
|
73
|
+
static parseNegations(content) {
|
|
74
|
+
const result = [];
|
|
75
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
76
|
+
const line = rawLine.trim();
|
|
77
|
+
if (line.startsWith('!') && line.length > 1) {
|
|
78
|
+
result.push(line.slice(1).trim());
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A re-include (`!`) szabályok egy könyvtárból: **`globs`** (a fájl-szintű re-include-matchhez, `<base>` +
|
|
85
|
+
* `<base>/**`) + **`bases`** (a `/**` nélküli alap-utak — a KÖNYVTÁR-prune lineage-ellenőrzéséhez: egy ignore-olt
|
|
86
|
+
* könyvtárat NEM vágunk le, ha egy re-include alatta/fölötte/rajta van). Hiányzó fájl → üres. Fail-safe.
|
|
87
|
+
*/
|
|
88
|
+
static loadReincludeRules(dirAbsolutePath, baseDir = '') {
|
|
89
|
+
try {
|
|
90
|
+
const filePath = path.join(dirAbsolutePath, FAM_FamIgnore_Util.FILENAME);
|
|
91
|
+
if (!fs.existsSync(filePath)) {
|
|
92
|
+
return { globs: [], bases: [] };
|
|
93
|
+
}
|
|
94
|
+
const negations = FAM_FamIgnore_Util.parseNegations(fs.readFileSync(filePath, 'utf-8'));
|
|
95
|
+
const globs = FAM_FamIgnore_Util.toGlobPatterns(negations, baseDir);
|
|
96
|
+
const bases = [...new Set(globs.map((glob) => glob.replace(/\/\*\*$/, '')))];
|
|
97
|
+
return { globs: globs, bases: bases };
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return { globs: [], bases: [] };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* `true`, ha a `dir` (root-relatív út) NEM prune-olható egy re-include miatt: (1) maga a dir re-included (vagy
|
|
105
|
+
* egy re-included alfa alatt van) — ezt **glob-match**-csel ellenőrizzük a re-include-`globs` ellen (így a
|
|
106
|
+
* `(glob)/client` minta a `client` dir-re, a `(glob)/client/(glob)` a `client/src` dir-re is illeszkedik); VAGY
|
|
107
|
+
* (2) a dir egy **anchored** (glob-mentes) re-include base ŐSE (van alatta re-included elem) — ezt string-prefixszel.
|
|
108
|
+
* (A bare-bázisok ős-esetét az (1) fedi.) Ez teszi a negációt traversal-helyessé (a szülőt nem vágjuk le idő előtt).
|
|
109
|
+
*/
|
|
110
|
+
static dirOnReincludeLineage(dir, reincludeGlobs, reincludeBases) {
|
|
111
|
+
const d = dir.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
112
|
+
if (fam_glob_match_util_1.FAM_GlobMatch_Util.matchesAny(d, reincludeGlobs)) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
return reincludeBases.some((rawBase) => {
|
|
116
|
+
const base = rawBase.replace(/\/+$/, '');
|
|
117
|
+
return !base.includes('*') && base.startsWith(`${d}/`);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
65
120
|
/**
|
|
66
121
|
* A `.fdpfamignore` beolvasása + fordítása egy könyvtárból (a `dirAbsolutePath`-ban). Hiányzó / olvashatatlan
|
|
67
122
|
* fájl → `[]` (opt-in: csak ha létezik). A `baseDir` a root-relatív horgony (a gyökér-szintű hívásnál `''`).
|
|
123
|
+
* (Csak az IGNORE minták — a re-include-ot a `loadReincludeRules` adja.)
|
|
68
124
|
*/
|
|
69
125
|
static loadFromDir(dirAbsolutePath, baseDir = '') {
|
|
70
126
|
try {
|