@futdevpro/fdp-agent-memory 1.1.21 → 1.1.112

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 (89) hide show
  1. package/build/package.json +2 -2
  2. package/build/src/_cli/_commands/find-duplicates.command.js +41 -16
  3. package/build/src/_cli/_commands/scan-projects.command.js +113 -113
  4. package/build/src/_cli/_commands/scan.command.js +2 -0
  5. package/build/src/_cli/_commands/serve.command.js +110 -10
  6. package/build/src/_cli/_services/fam-client.service.js +25 -1
  7. package/build/src/_cli/register-commands.js +28 -0
  8. package/build/src/_collections/config-catalog.const.js +152 -33
  9. package/build/src/_collections/error-codes.const.js +2 -0
  10. package/build/src/_collections/fam-console.util.js +38 -260
  11. package/build/src/_collections/fam-db-models.const.js +2 -0
  12. package/build/src/_collections/fam-entry-bootstrap.util.js +20 -6
  13. package/build/src/_collections/fam-error-context.util.js +1 -0
  14. package/build/src/_collections/fam-mcp-bridge.util.js +15 -1
  15. package/build/src/_collections/fam-operation-queue.service.js +85 -0
  16. package/build/src/_collections/fam-project-discovery.util.js +148 -0
  17. package/build/src/_collections/fam-request-origin.util.js +41 -0
  18. package/build/src/_collections/fam-retry.util.js +48 -0
  19. package/build/src/_collections/fam-scan-progress-sink.service.js +26 -0
  20. package/build/src/_collections/title.const.js +26 -26
  21. package/build/src/_enums/fam-rule-scope.type-enum.js +25 -0
  22. package/build/src/_models/data-models/fam-entry.data-model.js +29 -0
  23. package/build/src/_models/data-models/fam-memory.data-model.js +5 -0
  24. package/build/src/_models/data-models/fam-rules.data-model.js +24 -0
  25. package/build/src/_models/data-models/fam-scan-job.data-model.js +73 -0
  26. package/build/src/_models/data-models/fam-scope.data-model.js +9 -0
  27. package/build/src/_modules/capture/_services/fam-auto-capture.control-service.js +1 -1
  28. package/build/src/_modules/embedding/_services/fam-dedup-warn.control-service.js +40 -0
  29. package/build/src/_modules/embedding/_services/fam-duplicate-scan.control-service.js +74 -16
  30. package/build/src/_modules/embedding/_services/fam-embedding-bootstrap.control-service.js +50 -0
  31. package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +32 -6
  32. package/build/src/_modules/embedding/_services/fam-embedding.control-service.js +142 -9
  33. package/build/src/_modules/embedding/_services/fam-entry.data-service.js +32 -1
  34. package/build/src/_modules/embedding/_services/fam-hydration-coordinator.control-service.js +159 -0
  35. package/build/src/_modules/embedding/_services/fam-lmstudio-embedding.provider.js +41 -4
  36. package/build/src/_modules/embedding/_services/fam-vector-search.control-service.js +67 -12
  37. package/build/src/_modules/embedding/index.js +5 -1
  38. package/build/src/_modules/ingest/_collections/fam-file-routing.util.js +10 -0
  39. package/build/src/_modules/ingest/_collections/fam-frontmatter.util.js +134 -0
  40. package/build/src/_modules/ingest/_collections/fam-project-identity.util.js +30 -0
  41. package/build/src/_modules/ingest/_collections/fam-scan-progress.util.js +5 -2
  42. package/build/src/_modules/ingest/_collections/fam-scan-summary.util.js +55 -29
  43. package/build/src/_modules/ingest/_collections/fam-split-chunker.util.js +139 -0
  44. package/build/src/_modules/ingest/_models/interfaces/fam-scan-job.interface.js +2 -0
  45. package/build/src/_modules/ingest/_services/fam-chunker.control-service.js +10 -0
  46. package/build/src/_modules/ingest/_services/fam-delta-compare.util.js +68 -1
  47. package/build/src/_modules/ingest/_services/fam-ingest-run.data-service.js +1 -1
  48. package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +177 -37
  49. package/build/src/_modules/ingest/_services/fam-scan-job.control-service.js +429 -0
  50. package/build/src/_modules/ingest/_services/fam-scan-job.data-service.js +34 -0
  51. package/build/src/_modules/ingest/_services/fam-scan.control-service.js +6 -4
  52. package/build/src/_modules/ingest/index.js +6 -1
  53. package/build/src/_modules/mcp/_collections/fam-core-tools.const.js +1 -1
  54. package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +264 -41
  55. package/build/src/_modules/mcp/_services/fam-read-tool.service.js +71 -1
  56. package/build/src/_modules/mcp/_services/fam-write-tool.service.js +36 -0
  57. package/build/src/_modules/migration/_services/fam-claude-mem-import.control-service.js +1 -1
  58. package/build/src/_modules/retrieval/_collections/fam-lexical-match.util.js +69 -0
  59. package/build/src/_modules/retrieval/_collections/fam-memory-activation.util.js +66 -0
  60. package/build/src/_modules/retrieval/_collections/fam-rule-injection.util.js +125 -0
  61. package/build/src/_modules/retrieval/_collections/fam-rule-propagation.util.js +95 -0
  62. package/build/src/_modules/retrieval/_collections/fam-rule-scope-context.util.js +74 -0
  63. package/build/src/_modules/retrieval/_services/fam-global-rule.control-service.js +82 -0
  64. package/build/src/_modules/retrieval/_services/fam-memory-cold-search.control-service.js +121 -0
  65. package/build/src/_modules/retrieval/_services/fam-memory-dormancy.control-service.js +99 -0
  66. package/build/src/_modules/retrieval/_services/fam-memory-reactivation.control-service.js +68 -0
  67. package/build/src/_modules/retrieval/_services/fam-retrieval-candidate.data-service.js +15 -2
  68. package/build/src/_modules/retrieval/_services/fam-retrieval.control-service.js +197 -5
  69. package/build/src/_modules/retrieval/_services/fam-rule-injection.control-service.js +102 -0
  70. package/build/src/_modules/retrieval/index.js +12 -1
  71. package/build/src/_modules/scope-reference/_collections/fam-scope-maintenance.util.js +134 -0
  72. package/build/src/_modules/scope-reference/_services/fam-scope-maintenance.control-service.js +423 -0
  73. package/build/src/_modules/scope-reference/_services/fam-scope-resolver.control-service.js +2 -2
  74. package/build/src/_modules/scope-reference/_services/fam-scope.data-service.js +25 -0
  75. package/build/src/_modules/scope-reference/index.js +4 -1
  76. package/build/src/_routes/server/api/api.controller.js +324 -23
  77. package/build/src/_routes/server/api/fam-doctor.control-service.js +156 -0
  78. package/build/src/_routes/server/config/config.control-service.js +2 -2
  79. package/build/src/app.server.js +29 -27
  80. package/client-dist/{chunk-YXHWCJ5O.js → chunk-GF3FJP7E.js} +71 -71
  81. package/client-dist/chunk-NMUCMQY6.js +1 -0
  82. package/client-dist/favicon.ico +0 -0
  83. package/client-dist/index.html +3 -3
  84. package/client-dist/main-YK4YIVAQ.js +2 -0
  85. package/package.json +2 -2
  86. package/build/src/_cli/_collections/fam-project-discovery.util.js +0 -98
  87. package/build/src/_collections/fam-heap-guard.control-service.js +0 -176
  88. package/client-dist/chunk-I77GXVAQ.js +0 -1
  89. package/client-dist/main-PJPEDVJT.js +0 -2
@@ -19,9 +19,11 @@ const fam_content_hash_util_1 = require("../_collections/fam-content-hash.util")
19
19
  const fam_git_repo_util_1 = require("../_collections/fam-git-repo.util");
20
20
  const fam_scan_reconcile_util_1 = require("../_collections/fam-scan-reconcile.util");
21
21
  const fam_secret_content_util_1 = require("../_collections/fam-secret-content.util");
22
+ const fam_frontmatter_util_1 = require("../_collections/fam-frontmatter.util");
22
23
  const fam_project_identity_util_1 = require("../_collections/fam-project-identity.util");
23
24
  const fam_scan_path_util_1 = require("../_collections/fam-scan-path.util");
24
25
  const fam_scan_progress_util_1 = require("../_collections/fam-scan-progress.util");
26
+ const fam_scan_progress_sink_service_1 = require("../../../_collections/fam-scan-progress-sink.service");
25
27
  const fam_scan_summary_util_1 = require("../_collections/fam-scan-summary.util");
26
28
  const scope_reference_2 = require("../../scope-reference");
27
29
  const fam_reference_data_model_1 = require("../../../_models/data-models/fam-reference.data-model");
@@ -59,6 +61,16 @@ class FAM_Ingest_ControlService {
59
61
  const sample = content.length > 8192 ? content.slice(0, 8192) : content;
60
62
  return sample.indexOf(String.fromCharCode(0)) !== -1;
61
63
  }
64
+ /**
65
+ * Tábla-vezérelt ADDITÍV (accumulate-only) szabály (user-FR 2026-06-24): a `memory` tár MINDIG content-addressed
66
+ * additív — a memória-bejegyzések sosem „íródnak felül azonos location-nél", így a kompaktálás-előtti állapot
67
+ * a forrás zsugorodása UTÁN is megmarad a FAM-ban (vesztességmentes superset). A többi tár (codebase/documents/
68
+ * rules/...) a szokásos `(fájl, chunkIndex)` delta-t használja; rájuk a `preserveHistory` request-flag kérhet
69
+ * explicit additívot. Pure + statikus (tesztelhető).
70
+ */
71
+ static isAccumulateOnly(table) {
72
+ return table === fam_table_type_enum_1.FAM_Table.memory;
73
+ }
62
74
  /** Default issuer az ingest-műveletekhez. */
63
75
  issuer = 'FAM_Ingest_ControlService';
64
76
  static getInstance() {
@@ -180,7 +192,7 @@ class FAM_Ingest_ControlService {
180
192
  });
181
193
  runId = openedRun._id ?? runId;
182
194
  // (4) Per-fájl feldolgozás (flow-limit kötegekben; a per-fájl hiba NEM buktatja a run-t).
183
- const verdicts = { new: 0, modified: 0, equal: 0, deleted: 0 };
195
+ const verdicts = { new: 0, modified: 0, equal: 0, deleted: 0, superseded: 0 };
184
196
  const errors = [];
185
197
  let chunkCount = 0;
186
198
  const flowLimit = await this.resolveFlowLimit(canonicalScope);
@@ -195,12 +207,21 @@ class FAM_Ingest_ControlService {
195
207
  ? 0
196
208
  : fam_cooldown_util_1.FAM_Cooldown_Util.resolveMs((await config_control_service_1.FAM_Config_ControlService.getInstance().resolve('scan.sectionCooldownMs', { scopePath: canonicalScope })).value);
197
209
  let lastProgressAt = startedAt;
210
+ // Sub-progress (user-FR 2026-06-22): a felderítés→embedding közti csend kitöltése — mit gyűjtöttünk be +
211
+ // hogyan route-oltuk (mely tárakba), MIELŐTT a throttle-olt feldolgozás-sorok elindulnak.
212
+ if (!isDryRun && total > 0) {
213
+ const scopeLabel = canonicalScope.map((ref) => ref.canonicalName).join(' > ') || '(globális)';
214
+ fsm_dynamo_1.DyFM_Log.info(`[famIngest] [${scopeLabel}] felderítés kész: ${total} fájl begyűjtve `
215
+ + `(${discovery.skippedFiles.length} kihagyva, tárak: ${discovery.tables.map((table) => String(table)).join('+') || '—'}) `
216
+ + '→ embedding indul');
217
+ }
198
218
  for (let i = 0; i < total; i += flowLimit) {
199
219
  const batch = discovery.routedFiles.slice(i, i + flowLimit);
200
220
  const results = await Promise.all(batch.map((file) => this.processFile({
201
221
  file: file, runId: runId, scopePath: canonicalScope,
202
222
  resolvedRoot: discovery.resolvedRoot,
203
223
  dryRun: isDryRun, issuer: issuer, errors: errors,
224
+ preserveHistory: request.preserveHistory === true,
204
225
  })));
205
226
  for (const result of results) {
206
227
  if (!result) {
@@ -215,10 +236,17 @@ class FAM_Ingest_ControlService {
215
236
  const now = Date.now();
216
237
  if (!isDryRun && (processed >= total || now - lastProgressAt >= progressIntervalMs)) {
217
238
  lastProgressAt = now;
239
+ const scopeLeaf = canonicalScope.length ? canonicalScope[canonicalScope.length - 1].canonicalName : '';
218
240
  fsm_dynamo_1.DyFM_Log.info(fam_scan_progress_util_1.FAM_ScanProgress_Util.progressLine({
241
+ scopeLabel: scopeLeaf || undefined,
219
242
  processed: processed, total: total, chunkCount: chunkCount,
220
243
  verdicts: verdicts, elapsedSeconds: (now - startedAt) / 1000,
221
244
  }));
245
+ // Sub-progress sink (user-FR): a within-project haladás a scan-job status-ába → a thin-CLI poll is látja.
246
+ fam_scan_progress_sink_service_1.FAM_ScanProgress_Sink.set({
247
+ scopeLabel: scopeLeaf, processed: processed, total: total, chunkCount: chunkCount,
248
+ pct: total ? Math.round((processed / total) * 100) : 100, updatedAt: now,
249
+ });
222
250
  }
223
251
  // Szekció-cooldown: a batch-ek KÖZÉ (NEM az utolsó után) — opcionális gép-tehermentesítő szünet.
224
252
  if (sectionCooldownMs > 0 && processed < total) {
@@ -228,7 +256,11 @@ class FAM_Ingest_ControlService {
228
256
  // (4b) Törölt-fájl reconciliation (whole-file orphan) — CSAK folder/project scan (a `scan-file`
229
257
  // egyetlen fájlt céloz, nem reconciliál). A lemezről eltűnt fájlok chunkjai soft-delete-elődnek; a
230
258
  // számuk a `verdicts.deleted`-be (a per-fájl, fájlon-belüli `deleted`-ek mellé).
231
- if (request.operation !== 'scan-file') {
259
+ // ADDITÍV mód (user-FR 2026-06-22/24): `preserveHistory` request-flag VAGY a `memory` tár (tábla-vezérelt)
260
+ // a reconcile-törlés KIHAGYVA (a FAM superset-ként megtartja a forrásból eltűnt — pl. kompaktálódó memóriám —
261
+ // bejegyzéseit is; nincs destruktív felülírás). A `memory` ezért SOHA nem reconciliál, flag nélkül sem.
262
+ if (request.operation !== 'scan-file' && !request.preserveHistory
263
+ && !(request.tableOverride !== undefined && FAM_Ingest_ControlService.isAccumulateOnly(request.tableOverride))) {
232
264
  const reconcileIgnored = await this.resolveReconcileIgnored(canonicalScope);
233
265
  verdicts.deleted += await this.reconcileDeletedFiles({
234
266
  discovery: discovery, scopePath: canonicalScope, issuer: issuer, dryRun: request.dryRun ?? false,
@@ -284,7 +316,7 @@ class FAM_Ingest_ControlService {
284
316
  * soft-delete). A per-fájl hiba `errors[]`-be (no-silent-failure) + `null` visszatérés (a run nem bukik).
285
317
  */
286
318
  async processFile(set) {
287
- const table = set.file.route;
319
+ let table = set.file.route;
288
320
  try {
289
321
  const rawContent = fs.readFileSync(set.file.absolutePath, 'utf-8');
290
322
  // Tartalmi secret-redakció (user-FR): a forrásba hardcode-olt API-kulcs (sk-/AKIA/ghp_/PEM/JWT) a tárolt
@@ -294,7 +326,16 @@ class FAM_Ingest_ControlService {
294
326
  if (redaction.count > 0) {
295
327
  fsm_dynamo_1.DyFM_Log.warn(`[famIngest] secret-redakció: ${redaction.count} token redaktálva (${set.file.relativePath})`);
296
328
  }
297
- const content = redaction.text;
329
+ // MP-3 / F-A: fájl-fej `---`-frontmatter → ingest-irányítók + fejléc-mentes TÖRZS. Frontmatter nélkül NO-OP
330
+ // (`body===content`). A `table`-override (pl. `table: rules`) + `splitMode` + a per-entry rule-mezők innen jönnek.
331
+ const front = fam_frontmatter_util_1.FAM_Frontmatter_Util.parse(redaction.text);
332
+ const directives = front.directives;
333
+ const overrideTable = this.resolveDirectiveTable(directives.table);
334
+ if (overrideTable) {
335
+ table = overrideTable;
336
+ }
337
+ const splitMode = directives.splitMode === true ? 'heading' : (directives.splitMode || undefined);
338
+ const content = front.body;
298
339
  // FAM-REV-064 bináris-tartalom guard: a denylist-en átcsúszott, ismeretlen-kiterjesztésű bináris
299
340
  // (null-byte az első ~8KB-ban) NE kerüljön a generic chunkerbe (szemét-chunk ellen). Skip → 0 chunk.
300
341
  if (FAM_Ingest_ControlService.isLikelyBinary(content)) {
@@ -302,6 +343,7 @@ class FAM_Ingest_ControlService {
302
343
  }
303
344
  const chunks = await fam_chunker_control_service_1.FAM_Chunker_ControlService.getInstance().chunkFile({
304
345
  content: content, relativePath: set.file.relativePath, table: table, scopePath: set.scopePath,
346
+ splitMode: splitMode,
305
347
  });
306
348
  const registryEntry = embedding_1.FAM_StoreRegistry_Util.getEntry(table);
307
349
  if (!registryEntry) {
@@ -311,11 +353,17 @@ class FAM_Ingest_ControlService {
311
353
  // Existing-by-key betöltés a KANONIKUS abszolút útvonalon (FAM-REV-050) — a scan-root-független
312
354
  // fájl-identitás, így a delta bármely scan-gyökérről ugyanazt a fájlt matcheli (nincs duplikáció).
313
355
  const existing = await this.loadExistingChunks(dataService, set.file.absolutePath, set.scopePath);
314
- const deltaItems = fam_delta_compare_util_1.FAM_DeltaCompare_Util.compare(chunks, existing);
356
+ // ADDITÍV (content-addressed) delta → a régi (kompaktálás-előtti) tartalom MEGMARAD, sose `deleted`/`modified`
357
+ // (a FAM a memória minden valaha-látott állapotának vesztességmentes uniója). TÁBLA-VEZÉRELT (user-FR
358
+ // 2026-06-24): a `memory` tár MINDIG additív (a „felülírás azonos location-nél" csak kódbázis/dok/rules);
359
+ // a `preserveHistory` request-flag pedig bármely tárra explicit additív szkent kérhet.
360
+ const accumulateOnly = set.preserveHistory === true || FAM_Ingest_ControlService.isAccumulateOnly(table);
361
+ const deltaItems = fam_delta_compare_util_1.FAM_DeltaCompare_Util.compare(chunks, existing, accumulateOnly);
315
362
  if (!set.dryRun) {
316
363
  await this.applyVerdicts({
317
364
  deltaItems: deltaItems, file: set.file, table: table, runId: set.runId,
318
365
  scopePath: set.scopePath, root: set.resolvedRoot, dataService: dataService,
366
+ directives: directives,
319
367
  });
320
368
  }
321
369
  return {
@@ -373,6 +421,10 @@ class FAM_Ingest_ControlService {
373
421
  // re-embed nélkül (a friss chunk charStart/charEnd/sor-pozíciója felülír, ha eltér).
374
422
  await set.dataService.backfillPosition(item.existingId, item.chunk.position);
375
423
  }
424
+ // Git-provenance backfill (user-FR 2026-06-22): a CSAK-metaadat frissítés — a git-provenance
425
+ // feature ELŐTT ingestelt korpusz `equal`-chunkjai is megkapják a repoUrl/repoRelativePath/
426
+ // repoBranch-et RE-EMBED NÉLKÜL (a control-service számolja, így nincs data-service↔ingest ciklus).
427
+ await set.dataService.backfillGitProvenance(item.existingId, fam_git_repo_util_1.FAM_GitRepo_Util.sourceFieldsFor(set.file.absolutePath));
376
428
  }
377
429
  catch {
378
430
  // best-effort metaadat-frissítés — a content már naprakész (equal), nem eszkaláljuk.
@@ -384,7 +436,7 @@ class FAM_Ingest_ControlService {
384
436
  await this.persistAndEmbed({
385
437
  chunk: item.chunk, existingId: item.existingId, contentHash: item.contentHash,
386
438
  file: set.file, table: set.table, runId: set.runId, scopePath: set.scopePath,
387
- root: set.root, dataService: set.dataService,
439
+ root: set.root, dataService: set.dataService, directives: set.directives,
388
440
  });
389
441
  }
390
442
  }
@@ -398,12 +450,21 @@ class FAM_Ingest_ControlService {
398
450
  }
399
451
  continue;
400
452
  }
453
+ if (item.verdict === 'superseded') {
454
+ // dsgn-013 §6 (MAM F4): a fájl régi, már átírt tartalma — NEM törlés (a content + vektor MEGMARAD a
455
+ // poolban is, cold-kereshető), csak `superseded=true` jelölés → a memory-ranking penalizálja (a friss
456
+ // verzió fölébe kerül). A pool-ból NEM vesszük ki (a penalty süllyeszti; a dormancy idővel de-hidratálja).
457
+ if (item.existingId) {
458
+ await set.dataService.setSuperseded(item.existingId, true);
459
+ }
460
+ continue;
461
+ }
401
462
  // new / modified → upsert + embed (a `chunk` itt sosem null).
402
463
  if (item.chunk) {
403
464
  await this.persistAndEmbed({
404
465
  chunk: item.chunk, existingId: item.existingId, contentHash: item.contentHash,
405
466
  file: set.file, table: set.table, runId: set.runId, scopePath: set.scopePath,
406
- root: set.root, dataService: set.dataService,
467
+ root: set.root, dataService: set.dataService, directives: set.directives,
407
468
  });
408
469
  }
409
470
  }
@@ -437,6 +498,9 @@ class FAM_Ingest_ControlService {
437
498
  addedBy: 'scan',
438
499
  ingestRunId: set.runId,
439
500
  });
501
+ // MP-3 / F-A: a frontmatter per-entry rule-mezőit (importance/userApproved/ruleScope/…) + tageket rákötjük.
502
+ // CSAK definiált értékek (Object.assign-nal) — a nem-rules táron a nem-deklarált mezők úgyis nem persistálnak.
503
+ this.applyDirectiveFields(entry, set.directives);
440
504
  // Persist (saveData = upsert: meglévő `_id` → modify, hiányzó → insert).
441
505
  const saved = await set.dataService.saveData(entry);
442
506
  // Embed CSAK a new/modified-re (MP-2). Az embed-hiba az entry-t `error`-státuszba teszi +
@@ -445,6 +509,51 @@ class FAM_Ingest_ControlService {
445
509
  table: set.table, entry: saved, callType: 'embed-write',
446
510
  });
447
511
  }
512
+ /** A frontmatter `table:` direktíva → érvényes `FAM_Table` (ismeretlen → undefined, a file-routing marad). */
513
+ resolveDirectiveTable(raw) {
514
+ if (!raw) {
515
+ return undefined;
516
+ }
517
+ return Object.values(fam_table_type_enum_1.FAM_Table).find((value) => value === raw);
518
+ }
519
+ /**
520
+ * MP-3 / F-A: a frontmatter per-entry rule-mezőit + tageket az `FAM_Entry`-re köti (CSAK definiált értékek). A
521
+ * nem-`rules` táron a nem-deklarált mezők úgyis nem persistálnak (a dataParams.properties dönt), így ártalmatlan.
522
+ */
523
+ applyDirectiveFields(entry, directives) {
524
+ if (!directives) {
525
+ return;
526
+ }
527
+ const target = entry;
528
+ if (typeof directives.importance === 'number') {
529
+ target.importance = directives.importance;
530
+ }
531
+ if (typeof directives.userApproved === 'boolean') {
532
+ target.userApproved = directives.userApproved;
533
+ }
534
+ if (typeof directives.globalRule === 'boolean') {
535
+ target.globalRule = directives.globalRule;
536
+ }
537
+ if (directives.ruleScope) {
538
+ target.ruleScope = directives.ruleScope;
539
+ }
540
+ if (directives.ruleScopeTopic) {
541
+ target.ruleScopeTopic = directives.ruleScopeTopic;
542
+ }
543
+ if (directives.ruleId) {
544
+ target.ruleId = directives.ruleId;
545
+ }
546
+ if (directives.derivedFrom) {
547
+ target.derivedFrom = directives.derivedFrom;
548
+ }
549
+ if (directives.category) {
550
+ target.category = directives.category;
551
+ }
552
+ if (directives.tags && directives.tags.length) {
553
+ const existing = Array.isArray(entry.tags) ? entry.tags : [];
554
+ target.tags = [...new Set([...existing, ...directives.tags])];
555
+ }
556
+ }
448
557
  /**
449
558
  * A file-rendszer-összefoglaló (`knowledge`, kind `fs-summary`) generálása + idempotens upsert-je a scan után.
450
559
  * A `FAM_ScanSummary_Util` a mappastruktúrából építi a kereshető „mi van a projektben" szöveget. **Identitás:**
@@ -459,15 +568,16 @@ class FAM_Ingest_ControlService {
459
568
  }
460
569
  const dataService = new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: set.issuer });
461
570
  const scopeLabel = set.scopePath.map((ref) => ref.canonicalName).join(' > ') || '(globális)';
462
- const content = fam_scan_summary_util_1.FAM_ScanSummary_Util.build({
571
+ // NO-LOSS split (user-FR 2026-06-22): nagy fánál TÖBB, egyenlő részre bontva — minden fájl szerepel,
572
+ // semmit nem nyelünk le. Részenként EGY-EGY `knowledge`-entry (`chunkIndex` = rész-index).
573
+ const parts = fam_scan_summary_util_1.FAM_ScanSummary_Util.buildParts({
463
574
  scopeLabel: scopeLabel,
464
575
  root: set.discovery.resolvedRoot,
465
576
  relativePaths: set.discovery.routedFiles.map((file) => file.relativePath),
466
577
  skippedCount: set.discovery.skippedFiles.length,
467
578
  tables: set.discovery.tables.map((table) => String(table)),
468
579
  });
469
- const contentHash = fam_content_hash_util_1.FAM_ContentHash_Util.hash(content);
470
- // Meglévő összefoglaló a (source-type + scan-gyökér + levél-scope) identitáson.
580
+ // A meglévő összefoglaló-részek a (source-type + scan-gyökér + levél-scope) identitáson, rész-index szerint.
471
581
  const leafScopeId = set.scopePath.length ? set.scopePath[set.scopePath.length - 1].scopeId : undefined;
472
582
  const filter = {};
473
583
  filter['source.type'] = fam_scan_summary_util_1.FAM_ScanSummary_Util.SOURCE_TYPE;
@@ -477,33 +587,54 @@ class FAM_Ingest_ControlService {
477
587
  filter['scopePath.scopeId'] = leafScopeId;
478
588
  }
479
589
  const existing = await dataService.findHydratableList(filter);
480
- const existingEntry = existing[0];
481
- // Változatlan fájl-lista NO-OP (nincs fölös re-embed).
482
- if (existingEntry && existingEntry.contentHash === contentHash) {
483
- return;
590
+ const existingByIndex = new Map();
591
+ for (const item of existing) {
592
+ existingByIndex.set(item.chunkIndex ?? 0, item); // régi egy-részes entry: chunkIndex undefined → 0
593
+ }
594
+ let changed = 0;
595
+ for (let index = 0; index < parts.length; index++) {
596
+ const partContent = parts[index];
597
+ const partHash = fam_content_hash_util_1.FAM_ContentHash_Util.hash(partContent);
598
+ const existingPart = existingByIndex.get(index);
599
+ if (existingPart?.contentHash === partHash) {
600
+ continue; // változatlan rész → NO-OP (nincs fölös re-embed)
601
+ }
602
+ const entry = new fam_entry_data_model_1.FAM_Entry({
603
+ _id: existingPart?._id,
604
+ table: fam_table_type_enum_1.FAM_Table.knowledge,
605
+ content: partContent,
606
+ contentHash: partHash,
607
+ embeddingStatus: 'pending',
608
+ scopePath: set.scopePath,
609
+ chunkIndex: index,
610
+ kind: fam_scan_summary_util_1.FAM_ScanSummary_Util.KIND,
611
+ tags: ['fs-summary', 'filesystem', 'scan-summary'],
612
+ weight: 2,
613
+ source: {
614
+ type: 'scan-summary', root: set.discovery.resolvedRoot, absolutePath: set.discovery.resolvedRoot,
615
+ },
616
+ addedBy: 'scan',
617
+ ingestRunId: set.runId,
618
+ });
619
+ const saved = await dataService.saveData(entry);
620
+ await embedding_1.FAM_EmbeddingPipeline_ControlService.getInstance().embedAndPersist({
621
+ table: fam_table_type_enum_1.FAM_Table.knowledge, entry: saved, callType: 'embed-write',
622
+ });
623
+ changed++;
624
+ }
625
+ // Fölös részek (egy korábbi, NAGYOBB összefoglaló most-már-nem-létező részei) → soft-delete (no-orphan).
626
+ let removed = 0;
627
+ for (const item of existing) {
628
+ if ((item.chunkIndex ?? 0) >= parts.length) {
629
+ await dataService.deleteData(item._id);
630
+ removed++;
631
+ }
632
+ }
633
+ if (changed > 0 || removed > 0) {
634
+ fsm_dynamo_1.DyFM_Log.info(`[famIngest] fs-summary (${scopeLabel}): ${parts.length} rész `
635
+ + `(${changed} írva/frissítve, ${removed} fölös törölve), ${set.discovery.routedFiles.length} fájl, `
636
+ + `${set.discovery.tables.length} tár`);
484
637
  }
485
- const entry = new fam_entry_data_model_1.FAM_Entry({
486
- _id: existingEntry?._id,
487
- table: fam_table_type_enum_1.FAM_Table.knowledge,
488
- content: content,
489
- contentHash: contentHash,
490
- embeddingStatus: 'pending',
491
- scopePath: set.scopePath,
492
- kind: fam_scan_summary_util_1.FAM_ScanSummary_Util.KIND,
493
- tags: ['fs-summary', 'filesystem', 'scan-summary'],
494
- weight: 2,
495
- source: {
496
- type: 'scan-summary', root: set.discovery.resolvedRoot, absolutePath: set.discovery.resolvedRoot,
497
- },
498
- addedBy: 'scan',
499
- ingestRunId: set.runId,
500
- });
501
- const saved = await dataService.saveData(entry);
502
- await embedding_1.FAM_EmbeddingPipeline_ControlService.getInstance().embedAndPersist({
503
- table: fam_table_type_enum_1.FAM_Table.knowledge, entry: saved, callType: 'embed-write',
504
- });
505
- fsm_dynamo_1.DyFM_Log.info(`[famIngest] fs-summary ${existingEntry ? 'frissítve' : 'létrehozva'} `
506
- + `(${scopeLabel}): ${set.discovery.routedFiles.length} fájl, ${set.discovery.tables.length} tár`);
507
638
  }
508
639
  /**
509
640
  * A scan-gyökér PROJEKT-IDENTITÁSA (git + `package.json`) → (a) kereshető `knowledge` „project-identity"
@@ -558,6 +689,15 @@ class FAM_Ingest_ControlService {
558
689
  // (b) reference-alias (a package-név + rövid alakok → a projekt scope-ja) — string/exact-match feloldás.
559
690
  if (leaf) {
560
691
  await this.persistProjectReference(identity, leaf, set.discovery.resolvedRoot, set.issuer);
692
+ // (c) kanonikus repo-kulcs stamp a leaf project-scope-ra (scope-prevenció, user-FR 2026-06-22) — a STABIL
693
+ // git/package identitás scope-szintű rögzítése; erre épül a repo-kulcs alapú duplikátum-detekció, ami
694
+ // a KÜLÖNBÖZŐ nevű, AZONOS repo eseteket (import vs scan) is elkapja. Best-effort (nincs kulcs → no-op).
695
+ // path-aware kulcs (2026-06-24 fix): a projekt-mappa neve diszkriminátorként → a monorepo/fork
696
+ // sub-projektek külön repoKey-t kapnak (nem keverednek egy scope-ba egy téves merge-dzsel).
697
+ const repoKey = fam_project_identity_util_1.FAM_ProjectIdentity_Util.canonicalRepoKey(identity, path.basename(set.discovery.resolvedRoot));
698
+ if (repoKey) {
699
+ await new scope_reference_2.FAM_Scope_DataService({ issuer: set.issuer }).setCanonicalRepoKey(leaf.scopeId, repoKey);
700
+ }
561
701
  }
562
702
  fsm_dynamo_1.DyFM_Log.info(`[famIngest] project-identity (${scopeLabel}): `
563
703
  + `${identity.packageName ?? '(nincs package.json)'}${identity.isGit ? ' [git]' : ''}`
@@ -693,7 +833,7 @@ class FAM_Ingest_ControlService {
693
833
  ingestRunId: set.runId, trigger: set.request.operation, table: [],
694
834
  scopePath: set.scopePath, rootPath: set.request.path,
695
835
  filesProcessed: 0, filesSkipped: 0, chunkCount: 0,
696
- verdicts: { new: 0, modified: 0, equal: 0, deleted: 0 },
836
+ verdicts: { new: 0, modified: 0, equal: 0, deleted: 0, superseded: 0 },
697
837
  durationMs: Date.now() - set.startedAt, status: 'failed', errors: [errorEntry],
698
838
  };
699
839
  try {