@futdevpro/fdp-agent-memory 1.1.14 → 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.
- package/build/package.json +2 -2
- package/build/src/_cli/_commands/config.command.js +4 -1
- package/build/src/_cli/_commands/scan-projects.command.js +27 -2
- package/build/src/_collections/config-catalog.const.js +34 -1
- 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/title.const.js +31 -0
- package/build/src/_modules/capture/_services/fam-auto-capture.control-service.js +1 -1
- package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +1 -1
- package/build/src/_modules/embedding/_services/fam-entry.data-service.js +23 -1
- package/build/src/_modules/ingest/_collections/fam-famignore.util.js +57 -1
- 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 +40 -9
- package/build/src/_modules/ingest/_services/fam-scan.control-service.js +77 -11
- package/build/src/_modules/mcp/_collections/fam-core-tools.const.js +1 -1
- package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +113 -33
- package/build/src/_modules/mcp/_services/fam-read-tool.service.js +1 -1
- package/build/src/_modules/migration/_services/fam-claude-mem-import.control-service.js +1 -1
- package/build/src/_modules/scope-reference/_collections/fam-scope-maintenance.util.js +58 -0
- package/build/src/_modules/scope-reference/_services/fam-scope-maintenance.control-service.js +190 -0
- package/build/src/_modules/scope-reference/_services/fam-scope-resolver.control-service.js +2 -2
- package/build/src/_modules/scope-reference/index.js +4 -1
- package/build/src/_routes/server/api/api.controller.js +85 -18
- package/build/src/_routes/server/config/config.control-service.js +2 -2
- package/build/src/app.server.js +30 -1
- package/package.json +2 -2
|
@@ -14,8 +14,11 @@ const fam_entry_data_model_1 = require("../../../_models/data-models/fam-entry.d
|
|
|
14
14
|
const fam_reference_code_util_1 = require("../../../_collections/fam-reference-code.util");
|
|
15
15
|
const embedding_1 = require("../../embedding");
|
|
16
16
|
const scope_reference_1 = require("../../scope-reference");
|
|
17
|
+
const fam_cooldown_util_1 = require("../../../_collections/fam-cooldown.util");
|
|
17
18
|
const fam_content_hash_util_1 = require("../_collections/fam-content-hash.util");
|
|
18
19
|
const fam_git_repo_util_1 = require("../_collections/fam-git-repo.util");
|
|
20
|
+
const fam_scan_reconcile_util_1 = require("../_collections/fam-scan-reconcile.util");
|
|
21
|
+
const fam_secret_content_util_1 = require("../_collections/fam-secret-content.util");
|
|
19
22
|
const fam_project_identity_util_1 = require("../_collections/fam-project-identity.util");
|
|
20
23
|
const fam_scan_path_util_1 = require("../_collections/fam-scan-path.util");
|
|
21
24
|
const fam_scan_progress_util_1 = require("../_collections/fam-scan-progress.util");
|
|
@@ -186,6 +189,11 @@ class FAM_Ingest_ControlService {
|
|
|
186
189
|
// Futás-közbeni progress (descriptive user feedback): a NAGY (workspace-szintű) scan haladását
|
|
187
190
|
// throttle-olt `[famIngest]` sorokkal jelezzük a konzolon (a `dryRun` NEM embeddel → ott nincs progress).
|
|
188
191
|
const progressIntervalMs = await this.resolveScanNumber('scan.progressIntervalMs', canonicalScope, 2000);
|
|
192
|
+
// Kikapcsolható szekció-cooldown (user-FR): a NAGY scan ne terhelje túl a gépet — a batch-ek KÖZÉ
|
|
193
|
+
// opcionális szünet (default 0 = ki; dryRun-on sosem várunk, mert ott nincs valódi embed-munka).
|
|
194
|
+
const sectionCooldownMs = isDryRun
|
|
195
|
+
? 0
|
|
196
|
+
: fam_cooldown_util_1.FAM_Cooldown_Util.resolveMs((await config_control_service_1.FAM_Config_ControlService.getInstance().resolve('scan.sectionCooldownMs', { scopePath: canonicalScope })).value);
|
|
189
197
|
let lastProgressAt = startedAt;
|
|
190
198
|
for (let i = 0; i < total; i += flowLimit) {
|
|
191
199
|
const batch = discovery.routedFiles.slice(i, i + flowLimit);
|
|
@@ -212,13 +220,19 @@ class FAM_Ingest_ControlService {
|
|
|
212
220
|
verdicts: verdicts, elapsedSeconds: (now - startedAt) / 1000,
|
|
213
221
|
}));
|
|
214
222
|
}
|
|
223
|
+
// Szekció-cooldown: a batch-ek KÖZÉ (NEM az utolsó után) — opcionális gép-tehermentesítő szünet.
|
|
224
|
+
if (sectionCooldownMs > 0 && processed < total) {
|
|
225
|
+
await fam_cooldown_util_1.FAM_Cooldown_Util.sleep(sectionCooldownMs);
|
|
226
|
+
}
|
|
215
227
|
}
|
|
216
228
|
// (4b) Törölt-fájl reconciliation (whole-file orphan) — CSAK folder/project scan (a `scan-file`
|
|
217
229
|
// egyetlen fájlt céloz, nem reconciliál). A lemezről eltűnt fájlok chunkjai soft-delete-elődnek; a
|
|
218
230
|
// számuk a `verdicts.deleted`-be (a per-fájl, fájlon-belüli `deleted`-ek mellé).
|
|
219
231
|
if (request.operation !== 'scan-file') {
|
|
232
|
+
const reconcileIgnored = await this.resolveReconcileIgnored(canonicalScope);
|
|
220
233
|
verdicts.deleted += await this.reconcileDeletedFiles({
|
|
221
234
|
discovery: discovery, scopePath: canonicalScope, issuer: issuer, dryRun: request.dryRun ?? false,
|
|
235
|
+
reconcileIgnored: reconcileIgnored,
|
|
222
236
|
});
|
|
223
237
|
}
|
|
224
238
|
// (4c) File-rendszer összefoglaló (knowledge) — generált „mi van a projektben" entry a mappastruktúrából
|
|
@@ -272,7 +286,15 @@ class FAM_Ingest_ControlService {
|
|
|
272
286
|
async processFile(set) {
|
|
273
287
|
const table = set.file.route;
|
|
274
288
|
try {
|
|
275
|
-
const
|
|
289
|
+
const rawContent = fs.readFileSync(set.file.absolutePath, 'utf-8');
|
|
290
|
+
// Tartalmi secret-redakció (user-FR): a forrásba hardcode-olt API-kulcs (sk-/AKIA/ghp_/PEM/JWT) a tárolt
|
|
291
|
+
// szöveg/embedding ELŐTT redaktálódik → a titok sose kerül a DB-be (a contentHash is a redaktáltból → egy
|
|
292
|
+
// korábban-szivárgott fájl RE-SCANKOR `modified` → auto-gyógyul). A redaktálatlan kód-kontextus megmarad.
|
|
293
|
+
const redaction = fam_secret_content_util_1.FAM_SecretContent_Util.redact(rawContent);
|
|
294
|
+
if (redaction.count > 0) {
|
|
295
|
+
fsm_dynamo_1.DyFM_Log.warn(`[famIngest] secret-redakció: ${redaction.count} token redaktálva (${set.file.relativePath})`);
|
|
296
|
+
}
|
|
297
|
+
const content = redaction.text;
|
|
276
298
|
// FAM-REV-064 bináris-tartalom guard: a denylist-en átcsúszott, ismeretlen-kiterjesztésű bináris
|
|
277
299
|
// (null-byte az első ~8KB-ban) NE kerüljön a generic chunkerbe (szemét-chunk ellen). Skip → 0 chunk.
|
|
278
300
|
if (FAM_Ingest_ControlService.isLikelyBinary(content)) {
|
|
@@ -351,6 +373,10 @@ class FAM_Ingest_ControlService {
|
|
|
351
373
|
// re-embed nélkül (a friss chunk charStart/charEnd/sor-pozíciója felülír, ha eltér).
|
|
352
374
|
await set.dataService.backfillPosition(item.existingId, item.chunk.position);
|
|
353
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));
|
|
354
380
|
}
|
|
355
381
|
catch {
|
|
356
382
|
// best-effort metaadat-frissítés — a content már naprakész (equal), nem eszkaláljuk.
|
|
@@ -457,7 +483,7 @@ class FAM_Ingest_ControlService {
|
|
|
457
483
|
const existing = await dataService.findHydratableList(filter);
|
|
458
484
|
const existingEntry = existing[0];
|
|
459
485
|
// Változatlan fájl-lista → NO-OP (nincs fölös re-embed).
|
|
460
|
-
if (existingEntry
|
|
486
|
+
if (existingEntry?.contentHash === contentHash) {
|
|
461
487
|
return;
|
|
462
488
|
}
|
|
463
489
|
const entry = new fam_entry_data_model_1.FAM_Entry({
|
|
@@ -594,15 +620,15 @@ class FAM_Ingest_ControlService {
|
|
|
594
620
|
* így egy al-mappa scan a tágabb gyökérről ingestelt, de a saját subtree-jébe eső fájlokat is helyesen
|
|
595
621
|
* reconciliálja (és kívülre sosem nyúl). Az `absolutePath` nélküli (régi, migrálatlan) entry-ket kihagyja
|
|
596
622
|
* (no-migration safe). `dryRun` → csak számol. Visszaadja a reconciliált (soft-deleted) chunkok számát.
|
|
623
|
+
*
|
|
624
|
+
* **`reconcileIgnored` (user-FR 2026-06-21, default true):** a most ignore-mintával (hard/config/`.fdpfamignore`)
|
|
625
|
+
* kizárttá vált, de KORÁBBAN beolvasott fájlok is orphanná válnak → a régi chunkjaik kitakarítódnak (ne ragadjon
|
|
626
|
+
* be, amit ignore-olni akarunk). A present-set számítása a `FAM_ScanReconcile_Util.computePresentPaths`-ban (pure).
|
|
597
627
|
*/
|
|
598
628
|
async reconcileDeletedFiles(set) {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
}
|
|
603
|
-
for (const skipped of set.discovery.skippedFiles) {
|
|
604
|
-
presentPaths.add(skipped.absolutePath);
|
|
605
|
-
}
|
|
629
|
+
// A present-set (lemezen megtalált fájlok). `reconcileIgnored` → a MOST ignore-mintával kizárt fájl NEM present
|
|
630
|
+
// (a régi chunkja orphan → kitakarítható); a többi skip (secret/méret/unrouted) present marad (FAM_ScanReconcile_Util).
|
|
631
|
+
const presentPaths = fam_scan_reconcile_util_1.FAM_ScanReconcile_Util.computePresentPaths(set.discovery.routedFiles, set.discovery.skippedFiles, set.reconcileIgnored);
|
|
606
632
|
const leafScopeId = set.scopePath.length ? set.scopePath[set.scopePath.length - 1].scopeId : undefined;
|
|
607
633
|
let reconciled = 0;
|
|
608
634
|
for (const table of set.discovery.tables) {
|
|
@@ -651,6 +677,11 @@ class FAM_Ingest_ControlService {
|
|
|
651
677
|
const value = typeof resolved.value === 'number' ? resolved.value : 20;
|
|
652
678
|
return Math.min(Math.max(value, 1), 200);
|
|
653
679
|
}
|
|
680
|
+
/** Az ignore-add reconciliation (most-kizárt fájlok kitakarítása) a config-ból (`scan.reconcileIgnored`, default true). */
|
|
681
|
+
async resolveReconcileIgnored(scopePath) {
|
|
682
|
+
const resolved = await config_control_service_1.FAM_Config_ControlService.getInstance().resolve('scan.reconcileIgnored', { scopePath: scopePath });
|
|
683
|
+
return typeof resolved.value === 'boolean' ? resolved.value : true;
|
|
684
|
+
}
|
|
654
685
|
/** Egy scan-szám-config feloldása a scope-kontextussal + fallback-kel (a `scan.estimateMsPerChunk` / `scan.progressIntervalMs`-hez). */
|
|
655
686
|
async resolveScanNumber(key, scopePath, fallback) {
|
|
656
687
|
const resolved = await config_control_service_1.FAM_Config_ControlService.getInstance().resolve(key, { scopePath: scopePath });
|
|
@@ -51,16 +51,35 @@ class FAM_Scan_ControlService {
|
|
|
51
51
|
if (famIgnore.length) {
|
|
52
52
|
fsm_dynamo_1.DyFM_Log.info(`[famIngest] .fdpfamignore betöltve (${resolvedRoot}): ${famIgnore.length / 2} minta-sor`);
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
// KÉT külön ignore-réteg: a `hardIgnore` (config-default `node_modules`/`.git`/`build`/`dist`/… + config +
|
|
55
|
+
// input-exclude) — ezt a `!`-re-include SOHA nem éleszti fel (különben a `!client/` a `client/node_modules`-t
|
|
56
|
+
// is visszahozná → katasztrofális walk). A `softIgnore` (a .fdpfamignore `*`/saját mintái) — EZT írhatja felül
|
|
57
|
+
// a negáció. Így a „minden-kivéve-X" a hard-defaultokat tiszteletben tartja (eleve-tisztán-szkennelés).
|
|
58
|
+
const hardIgnore = baseIgnore;
|
|
59
|
+
const softIgnore = famIgnore;
|
|
60
|
+
// Re-include (`!`) szabályok a .fdpfamignore-ból (negáció — user-FR): a fájl-szintű re-include-match (`globs`)
|
|
61
|
+
// + a könyvtár-prune lineage-bázisok (`bases`). Üres a scan-file-nál / ha nincs negáció.
|
|
62
|
+
const reinclude = set.operation === 'scan-file'
|
|
63
|
+
? { globs: [], bases: [] }
|
|
64
|
+
: fam_famignore_util_1.FAM_FamIgnore_Util.loadReincludeRules(resolvedRoot);
|
|
55
65
|
const maxFileSizeBytes = await this.resolveMaxFileSize(set.scopePath);
|
|
56
66
|
const followSymlinks = await this.resolveFollowSymlinks(set.scopePath);
|
|
57
67
|
const testFileWeight = await this.resolveTestFileWeight(set.scopePath);
|
|
68
|
+
// NESTED `.fdpfamignore` (config-kapcsolós, default ON): a bejárás közben minden al-mappa saját famignore-ja
|
|
69
|
+
// is érvényesül (a saját al-útjához horgonyozva). A `collected` a walk-közben FELFEDEZETT nested-mintákat
|
|
70
|
+
// gyűjti, hogy a MÁSODIK (classify) kör is lássa őket (a minták al-út-horgonyzottak → globálisan egyértelműek).
|
|
71
|
+
const nestedEnabled = set.operation === 'scan-file' ? false : await this.resolveNestedFamignore(set.scopePath);
|
|
72
|
+
const collected = { soft: [], reGlobs: [], reBases: [] };
|
|
58
73
|
const routedFiles = [];
|
|
59
74
|
const skippedFiles = [];
|
|
60
75
|
// A felderített abszolút fájlok (file = 1 db; folder/project = rekurzív bejárás).
|
|
61
76
|
const absoluteFiles = set.operation === 'scan-file'
|
|
62
77
|
? [resolvedRoot]
|
|
63
|
-
: this.walkDirectory(resolvedRoot, resolvedRoot,
|
|
78
|
+
: this.walkDirectory(resolvedRoot, resolvedRoot, hardIgnore, softIgnore, reinclude, followSymlinks, { enabled: nestedEnabled, collected: collected });
|
|
79
|
+
// A classify-kör effektív soft-ignore + re-include halmaza: a gyökér-szintű + MINDEN walk-közben felfedezett
|
|
80
|
+
// nested minta (a nested fájl-szintű skip-ekhez — a dir-prune-t a walk már elvégezte). Üres collector → no-op.
|
|
81
|
+
const classifySoft = collected.soft.length ? [...softIgnore, ...collected.soft] : softIgnore;
|
|
82
|
+
const classifyReGlobs = collected.reGlobs.length ? [...reinclude.globs, ...collected.reGlobs] : reinclude.globs;
|
|
64
83
|
for (const absolutePath of absoluteFiles) {
|
|
65
84
|
const relativePath = set.operation === 'scan-file'
|
|
66
85
|
? path.basename(resolvedRoot)
|
|
@@ -68,7 +87,9 @@ class FAM_Scan_ControlService {
|
|
|
68
87
|
const routed = this.classifyFile({
|
|
69
88
|
absolutePath: absolutePath,
|
|
70
89
|
relativePath: relativePath,
|
|
71
|
-
|
|
90
|
+
hardIgnorePatterns: hardIgnore,
|
|
91
|
+
softIgnorePatterns: classifySoft,
|
|
92
|
+
reincludePatterns: classifyReGlobs,
|
|
72
93
|
includePatterns: set.include,
|
|
73
94
|
maxFileSizeBytes: maxFileSizeBytes,
|
|
74
95
|
tableOverride: set.tableOverride,
|
|
@@ -103,9 +124,16 @@ class FAM_Scan_ControlService {
|
|
|
103
124
|
if (fam_secret_exclude_util_1.FAM_SecretExclude_Util.isSecret(set.relativePath)) {
|
|
104
125
|
return { ...base, route: 'skip', skipReason: 'secret-exclude (filename/path-based; content-PII scan = BACKLOG)' };
|
|
105
126
|
}
|
|
106
|
-
// (
|
|
107
|
-
|
|
108
|
-
|
|
127
|
+
// (2a) HARD ignore (dsgn-004 §6.2) — node_modules/.git/build/dist/lock/… + a config + input-exclude.
|
|
128
|
+
// Ezt a `!`-re-include SOHA nem éleszti fel (a `!client/` ne hozza vissza a `client/node_modules`-t).
|
|
129
|
+
if (fam_glob_match_util_1.FAM_GlobMatch_Util.matchesAny(set.relativePath, set.hardIgnorePatterns)) {
|
|
130
|
+
return { ...base, route: 'skip', skipReason: 'ignore-pattern', excludedByIgnore: true };
|
|
131
|
+
}
|
|
132
|
+
// (2b) SOFT ignore (.fdpfamignore saját mintái) — EZT a `!`-RE-INCLUDE felülírja: soft-ignore-match SKIP,
|
|
133
|
+
// KIVÉVE ha re-include-match (negáció). Így a „minden-kivéve-X" csak a famignore-szintet vonja vissza.
|
|
134
|
+
if (set.softIgnorePatterns && fam_glob_match_util_1.FAM_GlobMatch_Util.matchesAny(set.relativePath, set.softIgnorePatterns)
|
|
135
|
+
&& !(set.reincludePatterns && fam_glob_match_util_1.FAM_GlobMatch_Util.matchesAny(set.relativePath, set.reincludePatterns))) {
|
|
136
|
+
return { ...base, route: 'skip', skipReason: 'ignore-pattern (famignore)', excludedByIgnore: true };
|
|
109
137
|
}
|
|
110
138
|
// (3) Include-szűrés (ha megadva: csak az illeszkedő fájl megy tovább, dsgn-003 §3.1).
|
|
111
139
|
if (set.includePatterns && set.includePatterns.length && !fam_glob_match_util_1.FAM_GlobMatch_Util.matchesAny(set.relativePath, set.includePatterns)) {
|
|
@@ -133,8 +161,13 @@ class FAM_Scan_ControlService {
|
|
|
133
161
|
* KÖNYVTÁRAKAT bejárás KÖZBEN kihagyja (ne lépjen `node_modules`-ba — teljesítmény + biztonság).
|
|
134
162
|
* A symlinkeket alapból NEM követi (`followSymlinks` config); a path-traversal guardot minden
|
|
135
163
|
* fájlra futtatja (a feloldott valódi útvonal a gyökér alatt kell maradjon).
|
|
164
|
+
*
|
|
165
|
+
* **NESTED `.fdpfamignore` (`nested.enabled`):** minden NEM-gyökér mappánál betölti a mappa saját famignore-ját
|
|
166
|
+
* (a mappa al-útjához horgonyozva), MERGE-eli az örökölt soft-ignore/re-include-ba (a leszármazottak öröklik), és
|
|
167
|
+
* a `nested.collected`-be is gyűjti (a discover második, classify-köre fájl-szinten is alkalmazza). A gyökér
|
|
168
|
+
* famignore-ját a hívó (`discover`) tölti — itt csak az al-mappákét.
|
|
136
169
|
*/
|
|
137
|
-
walkDirectory(currentDir, resolvedRoot,
|
|
170
|
+
walkDirectory(currentDir, resolvedRoot, hardIgnore, softIgnore, reinclude, followSymlinks, nested) {
|
|
138
171
|
const result = [];
|
|
139
172
|
let entries;
|
|
140
173
|
try {
|
|
@@ -144,6 +177,24 @@ class FAM_Scan_ControlService {
|
|
|
144
177
|
// Olvashatatlan könyvtár → kihagyjuk (a per-fájl olvasás-hiba a chunk-fázisban kezelt).
|
|
145
178
|
return result;
|
|
146
179
|
}
|
|
180
|
+
// NESTED famignore: a NEM-gyökér mappa saját `.fdpfamignore`-ja az al-útjához horgonyozva → MERGE az örököltbe
|
|
181
|
+
// (a leszármazottak ezt öröklik) + gyűjtés a classify-körhöz. A gyökér famignore-ját már a `discover` betöltötte.
|
|
182
|
+
let effSoftIgnore = softIgnore;
|
|
183
|
+
let effReinclude = reinclude;
|
|
184
|
+
if (nested.enabled && currentDir !== resolvedRoot) {
|
|
185
|
+
const baseDir = fam_scan_path_util_1.FAM_ScanPath_Util.toRelative(resolvedRoot, currentDir);
|
|
186
|
+
const nestedIgnore = fam_famignore_util_1.FAM_FamIgnore_Util.loadFromDir(currentDir, baseDir);
|
|
187
|
+
const nestedReinc = fam_famignore_util_1.FAM_FamIgnore_Util.loadReincludeRules(currentDir, baseDir);
|
|
188
|
+
if (nestedIgnore.length) {
|
|
189
|
+
effSoftIgnore = [...softIgnore, ...nestedIgnore];
|
|
190
|
+
nested.collected.soft.push(...nestedIgnore);
|
|
191
|
+
}
|
|
192
|
+
if (nestedReinc.globs.length) {
|
|
193
|
+
effReinclude = { globs: [...reinclude.globs, ...nestedReinc.globs], bases: [...reinclude.bases, ...nestedReinc.bases] };
|
|
194
|
+
nested.collected.reGlobs.push(...nestedReinc.globs);
|
|
195
|
+
nested.collected.reBases.push(...nestedReinc.bases);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
147
198
|
for (const entry of entries) {
|
|
148
199
|
const entryPath = path.join(currentDir, entry.name);
|
|
149
200
|
const relativePath = fam_scan_path_util_1.FAM_ScanPath_Util.toRelative(resolvedRoot, entryPath);
|
|
@@ -152,16 +203,26 @@ class FAM_Scan_ControlService {
|
|
|
152
203
|
continue;
|
|
153
204
|
}
|
|
154
205
|
if (entry.isDirectory()) {
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
206
|
+
// HARD ignore (node_modules/.git/build/…): a könyvtárat bejárás közben FELTÉTEL NÉLKÜL kihagyja —
|
|
207
|
+
// a `!`-re-include NEM élesztheti fel (a `!client/` ne lépjen `client/node_modules`-ba). Ez tartja a
|
|
208
|
+
// walkot eleve-tisztán (a katasztrofális node_modules-bejárás kizárva még re-included dir alatt is).
|
|
209
|
+
if (fam_glob_match_util_1.FAM_GlobMatch_Util.matchesAny(relativePath, hardIgnore)
|
|
210
|
+
|| fam_glob_match_util_1.FAM_GlobMatch_Util.matchesAny(relativePath + '/', hardIgnore)) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
// SOFT ignore (.fdpfamignore saját mintái): a mappát kihagyja — DE re-include-tudatosan: egy soft-ignore-olt
|
|
214
|
+
// mappát NEM vágunk le, ha re-included elem van rajta/alatta/fölötte (különben a `*` + `!client/` esetén a
|
|
215
|
+
// `client` szülőjét/magát levágnánk → a re-included client soha nem érhető el).
|
|
216
|
+
const softIgnored = fam_glob_match_util_1.FAM_GlobMatch_Util.matchesAny(relativePath, effSoftIgnore)
|
|
217
|
+
|| fam_glob_match_util_1.FAM_GlobMatch_Util.matchesAny(relativePath + '/', effSoftIgnore);
|
|
218
|
+
if (softIgnored && !fam_famignore_util_1.FAM_FamIgnore_Util.dirOnReincludeLineage(relativePath, effReinclude.globs, effReinclude.bases)) {
|
|
158
219
|
continue;
|
|
159
220
|
}
|
|
160
221
|
// Path-traversal guard (a feloldott valódi útvonal a gyökér alatt kell maradjon).
|
|
161
222
|
if (!this.isInsideRootSafe(resolvedRoot, entryPath)) {
|
|
162
223
|
continue;
|
|
163
224
|
}
|
|
164
|
-
result.push(...this.walkDirectory(entryPath, resolvedRoot,
|
|
225
|
+
result.push(...this.walkDirectory(entryPath, resolvedRoot, hardIgnore, effSoftIgnore, effReinclude, followSymlinks, nested));
|
|
165
226
|
continue;
|
|
166
227
|
}
|
|
167
228
|
if (entry.isFile()) {
|
|
@@ -225,6 +286,11 @@ class FAM_Scan_ControlService {
|
|
|
225
286
|
const resolved = await config_control_service_1.FAM_Config_ControlService.getInstance().resolve('scan.followSymlinks', { scopePath: scopePath });
|
|
226
287
|
return typeof resolved.value === 'boolean' ? resolved.value : false;
|
|
227
288
|
}
|
|
289
|
+
/** A nested (per-alkönyvtár) `.fdpfamignore` figyelembevétele a config-ból (`scan.nestedFamignore`, default true). */
|
|
290
|
+
async resolveNestedFamignore(scopePath) {
|
|
291
|
+
const resolved = await config_control_service_1.FAM_Config_ControlService.getInstance().resolve('scan.nestedFamignore', { scopePath: scopePath });
|
|
292
|
+
return typeof resolved.value === 'boolean' ? resolved.value : true;
|
|
293
|
+
}
|
|
228
294
|
/** A teszt-/spec-fájlok default súlya a config-ból (`scan.testFileWeight`, default 0.5 — FAM-REV-058). */
|
|
229
295
|
async resolveTestFileWeight(scopePath) {
|
|
230
296
|
const resolved = await config_control_service_1.FAM_Config_ControlService.getInstance().resolve('scan.testFileWeight', { scopePath: scopePath });
|
|
@@ -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
|
-
/**
|
|
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
|
|
569
|
-
const
|
|
621
|
+
const collection = this.entryCollection(registryEntry);
|
|
622
|
+
const count = await collection.countDocuments({});
|
|
570
623
|
const byStatus = { pending: 0, completed: 0, error: 0 };
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
756
|
-
|
|
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
|
|
869
|
-
|
|
870
|
-
|
|
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:
|
|
882
|
-
tags:
|
|
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
|
|
894
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
1093
|
-
|
|
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
|
|
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
|
|
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;
|