@futdevpro/fdp-agent-memory 1.1.30 → 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 (81) hide show
  1. package/build/package.json +1 -1
  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 +148 -6
  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/_enums/fam-rule-scope.type-enum.js +25 -0
  21. package/build/src/_models/data-models/fam-entry.data-model.js +29 -0
  22. package/build/src/_models/data-models/fam-memory.data-model.js +5 -0
  23. package/build/src/_models/data-models/fam-rules.data-model.js +24 -0
  24. package/build/src/_models/data-models/fam-scan-job.data-model.js +73 -0
  25. package/build/src/_models/data-models/fam-scope.data-model.js +9 -0
  26. package/build/src/_modules/embedding/_services/fam-dedup-warn.control-service.js +40 -0
  27. package/build/src/_modules/embedding/_services/fam-duplicate-scan.control-service.js +74 -16
  28. package/build/src/_modules/embedding/_services/fam-embedding-bootstrap.control-service.js +50 -0
  29. package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +31 -5
  30. package/build/src/_modules/embedding/_services/fam-embedding.control-service.js +142 -9
  31. package/build/src/_modules/embedding/_services/fam-entry.data-service.js +9 -0
  32. package/build/src/_modules/embedding/_services/fam-hydration-coordinator.control-service.js +159 -0
  33. package/build/src/_modules/embedding/_services/fam-lmstudio-embedding.provider.js +41 -4
  34. package/build/src/_modules/embedding/_services/fam-vector-search.control-service.js +67 -12
  35. package/build/src/_modules/embedding/index.js +5 -1
  36. package/build/src/_modules/ingest/_collections/fam-file-routing.util.js +10 -0
  37. package/build/src/_modules/ingest/_collections/fam-frontmatter.util.js +134 -0
  38. package/build/src/_modules/ingest/_collections/fam-project-identity.util.js +30 -0
  39. package/build/src/_modules/ingest/_collections/fam-scan-progress.util.js +5 -2
  40. package/build/src/_modules/ingest/_collections/fam-scan-summary.util.js +55 -29
  41. package/build/src/_modules/ingest/_collections/fam-split-chunker.util.js +139 -0
  42. package/build/src/_modules/ingest/_models/interfaces/fam-scan-job.interface.js +2 -0
  43. package/build/src/_modules/ingest/_services/fam-chunker.control-service.js +10 -0
  44. package/build/src/_modules/ingest/_services/fam-delta-compare.util.js +68 -1
  45. package/build/src/_modules/ingest/_services/fam-ingest-run.data-service.js +1 -1
  46. package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +173 -37
  47. package/build/src/_modules/ingest/_services/fam-scan-job.control-service.js +429 -0
  48. package/build/src/_modules/ingest/_services/fam-scan-job.data-service.js +34 -0
  49. package/build/src/_modules/ingest/_services/fam-scan.control-service.js +6 -4
  50. package/build/src/_modules/ingest/index.js +6 -1
  51. package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +151 -8
  52. package/build/src/_modules/mcp/_services/fam-read-tool.service.js +70 -0
  53. package/build/src/_modules/mcp/_services/fam-write-tool.service.js +36 -0
  54. package/build/src/_modules/retrieval/_collections/fam-lexical-match.util.js +69 -0
  55. package/build/src/_modules/retrieval/_collections/fam-memory-activation.util.js +66 -0
  56. package/build/src/_modules/retrieval/_collections/fam-rule-injection.util.js +125 -0
  57. package/build/src/_modules/retrieval/_collections/fam-rule-propagation.util.js +95 -0
  58. package/build/src/_modules/retrieval/_collections/fam-rule-scope-context.util.js +74 -0
  59. package/build/src/_modules/retrieval/_services/fam-global-rule.control-service.js +82 -0
  60. package/build/src/_modules/retrieval/_services/fam-memory-cold-search.control-service.js +121 -0
  61. package/build/src/_modules/retrieval/_services/fam-memory-dormancy.control-service.js +99 -0
  62. package/build/src/_modules/retrieval/_services/fam-memory-reactivation.control-service.js +68 -0
  63. package/build/src/_modules/retrieval/_services/fam-retrieval-candidate.data-service.js +15 -2
  64. package/build/src/_modules/retrieval/_services/fam-retrieval.control-service.js +197 -5
  65. package/build/src/_modules/retrieval/_services/fam-rule-injection.control-service.js +102 -0
  66. package/build/src/_modules/retrieval/index.js +12 -1
  67. package/build/src/_modules/scope-reference/_collections/fam-scope-maintenance.util.js +76 -0
  68. package/build/src/_modules/scope-reference/_services/fam-scope-maintenance.control-service.js +233 -0
  69. package/build/src/_modules/scope-reference/_services/fam-scope.data-service.js +25 -0
  70. package/build/src/_routes/server/api/api.controller.js +270 -0
  71. package/build/src/_routes/server/api/fam-doctor.control-service.js +156 -0
  72. package/build/src/app.server.js +13 -10
  73. package/client-dist/{chunk-YXHWCJ5O.js → chunk-GF3FJP7E.js} +71 -71
  74. package/client-dist/chunk-NMUCMQY6.js +1 -0
  75. package/client-dist/favicon.ico +0 -0
  76. package/client-dist/index.html +3 -3
  77. package/client-dist/main-YK4YIVAQ.js +2 -0
  78. package/package.json +1 -1
  79. package/build/src/_cli/_collections/fam-project-discovery.util.js +0 -98
  80. package/client-dist/chunk-I77GXVAQ.js +0 -1
  81. package/client-dist/main-PJPEDVJT.js +0 -2
@@ -8,6 +8,10 @@ const embedding_1 = require("../../embedding");
8
8
  const scope_reference_1 = require("../../scope-reference");
9
9
  const fam_retrieval_candidate_data_service_1 = require("./fam-retrieval-candidate.data-service");
10
10
  const fam_retrieval_suggestions_util_1 = require("./fam-retrieval-suggestions.util");
11
+ const fam_memory_activation_util_1 = require("../_collections/fam-memory-activation.util");
12
+ const fam_lexical_match_util_1 = require("../_collections/fam-lexical-match.util");
13
+ const fam_memory_reactivation_control_service_1 = require("./fam-memory-reactivation.control-service");
14
+ const fam_memory_cold_search_control_service_1 = require("./fam-memory-cold-search.control-service");
11
15
  /** A topK / lapozás abszolút plafonja (CCAP MAX_PAGE_SIZE=500; dsgn-005 §6). */
12
16
  const MAX_PAGE_SIZE = 500;
13
17
  /**
@@ -95,8 +99,57 @@ class FAM_Retrieval_ControlService {
95
99
  const relevantSet = visibleHits.filter((hit) => this.codeExactRank(hit) > 0 || hit.finalScore >= config.minScore);
96
100
  // ⑦ rendezés (finalScore desc → recency → _id asc).
97
101
  this.sortHits(relevantSet, config.scoreOrder);
102
+ // ⑦b R1 duplikátum-dedup (FAM-REV 2026-06-22, eval-lelet): az AZONOS-tartalmú (kereszt-repo) hitek összevonása
103
+ // a top-K ELŐTT — a kanonikus (legjobb-ranked) marad, a másolat-szám a `duplicateCount`-ban. A közös fájlok
104
+ // (`.cursor/rules` / CLAUDE.md / guideline) ne pazarolják a top-K-t 4-5 azonos másolattal. A `sortHits` UTÁN fut
105
+ // (a megtartott a legjobb-rangsorolt); a contentHash-mentes hitek érintetlenek.
106
+ const dedupedSet = FAM_Retrieval_ControlService.dedupeByContentHash(relevantSet);
98
107
  // ⑧ topK-vágás + dense-detektálás + suggestions.
99
- return this.buildResult({ query, relevantSet, config, scopeExpand, detectedCodes });
108
+ const result = this.buildResult({ query, relevantSet: dedupedSet, config, scopeExpand, detectedCodes });
109
+ // dsgn-013 §4 (MAM F2) — FELIDÉZÉS-megerősítés: a ténylegesen VISSZAADOTT `memory`-hitek „új energiát kapnak"
110
+ // (lastRecalledAt=now, recallCount++, dormant=false). ASYNC fire-and-forget — NEM blokkolja a read választ.
111
+ this.reactivateReturnedMemoryHits(result.hits, config);
112
+ return result;
113
+ }
114
+ /**
115
+ * A visszaadott top-K `memory`-hitek async felidézés-megerősítése (dsgn-013 §4). Fire-and-forget (void): a read
116
+ * már elkészült, a felidézés-frissítés best-effort. Csak a `memory`-táras, `_id`-vel bíró hitre; üres → no-op.
117
+ * `memoryReactivateOnRead=false` → kikapcsolva.
118
+ */
119
+ reactivateReturnedMemoryHits(hits, config) {
120
+ if (!config.memoryReactivateOnRead) {
121
+ return;
122
+ }
123
+ const memoryIds = fam_memory_reactivation_control_service_1.FAM_MemoryReactivation_ControlService.selectMemoryIds(hits);
124
+ if (!memoryIds.length) {
125
+ return;
126
+ }
127
+ void fam_memory_reactivation_control_service_1.FAM_MemoryReactivation_ControlService.getInstance().reactivate(memoryIds, Date.now());
128
+ }
129
+ /**
130
+ * R1 dedup (pure): az AZONOS `contentHash`-ű hitek összevonása — az ELSŐ (a `sortHits` után legjobb-ranked) marad,
131
+ * a többi azonos-tartalmú másolatot beleszámoljuk a megtartott `duplicateCount`-jába. A `contentHash`-mentes hitek
132
+ * (pl. compact-mode projekció nélkül) NEM dedup-olódnak (mind egyedi). Sorrend-tartó.
133
+ */
134
+ static dedupeByContentHash(hits) {
135
+ const keptByHash = new Map();
136
+ const result = [];
137
+ for (const hit of hits) {
138
+ const key = hit.contentHash;
139
+ if (!key) {
140
+ result.push(hit);
141
+ continue;
142
+ }
143
+ const kept = keptByHash.get(key);
144
+ if (kept) {
145
+ kept.duplicateCount = (kept.duplicateCount ?? 0) + 1;
146
+ }
147
+ else {
148
+ keptByHash.set(key, hit);
149
+ result.push(hit);
150
+ }
151
+ }
152
+ return result;
100
153
  }
101
154
  // =========================================================================
102
155
  // ④b KÓD-EXPANZIÓ (FEAT-003) — determinisztikus link-retrieval a vektor MELLÉ
@@ -171,6 +224,7 @@ class FAM_Retrieval_ControlService {
171
224
  weight: set.weight,
172
225
  finalScore: 1.0 * set.weight,
173
226
  content: set.includeContent ? set.entry.content : undefined,
227
+ contentHash: set.entry.contentHash,
174
228
  kind: set.entry.kind,
175
229
  tags: set.entry.tags ?? [],
176
230
  ...this.ruleMeta(set.entry, set.table),
@@ -256,6 +310,8 @@ class FAM_Retrieval_ControlService {
256
310
  });
257
311
  }
258
312
  // ⑤ weight-szorzó: finalScore = score × (weight ?? 1); weight ≤ 0 → kizárt (nem kerül a hits közé).
313
+ // Improvement 2: a query lexikai term-jeit EGYSZER tokenizáljuk (a hibrid keyword-boost-hoz a toHit-ban).
314
+ const queryTerms = fam_lexical_match_util_1.FAM_LexicalMatch_Util.tokenize(set.query.query);
259
315
  const hits = [];
260
316
  for (const vectorHit of vectorHits) {
261
317
  const entry = byId.get(vectorHit.id);
@@ -267,12 +323,63 @@ class FAM_Retrieval_ControlService {
267
323
  continue;
268
324
  }
269
325
  hits.push(this.toHit({
270
- entry, vectorHit, table: set.table, weight,
271
- importChunkWeight: set.config.importChunkWeight, includeContent: set.includeContent,
326
+ entry, vectorHit, table: set.table, weight, queryTerms: queryTerms,
327
+ importChunkWeight: set.config.importChunkWeight, importSourceWeight: set.config.importSourceWeight,
328
+ includeContent: set.includeContent, config: set.config, nowMs: Date.now(),
272
329
  }));
273
330
  }
331
+ // dsgn-013 §5.3 (MAM F3b) — COLD-search supplement: ha `memory` + engedélyezett + a hot pool kevés ERŐSEN-
332
+ // releváns találatot ad. FONTOS: NEM a candidate-darabszámot nézzük (a vektor-keresés ~MAX_PAGE_SIZE jelöltet ad
333
+ // vissza, így a `hits.length` szinte mindig nagy) — hanem az ERŐS (raw cosine ≥ relevanceFloor) hot hitek számát.
334
+ // Ha ezekből kevesebb mint topK van, a hot pool nem elégítette ki a query-t → a dormant készleten keresünk.
335
+ const strongHotHits = hits.filter((hit) => hit.score >= set.config.relevanceFloor).length;
336
+ if (set.table === fam_table_type_enum_1.FAM_Table.memory && set.config.memoryColdSearchEnabled && strongHotHits < set.config.topK) {
337
+ await this.supplementWithColdSearch(set, hits, set.query);
338
+ }
274
339
  return hits;
275
340
  }
341
+ /**
342
+ * A hot memória-találatok kiegészítése a dormant készletből (dsgn-013 §5.3). A cold-hiteket a `toHit`-en át
343
+ * (decay + faktorok) fűzzük be, a már látott `_id`-ket kiszűrve; a `weight ≤ 0` továbbra is kizárt.
344
+ */
345
+ async supplementWithColdSearch(set, hits, query) {
346
+ // `topK` cold-jelölt (NEM `topK - hits.length` — a `hits` itt a teljes candidate-halmaz, ~MAX_PAGE_SIZE, ami
347
+ // negatívot adna). A cold-hitek a NORMÁL ranking-pipeline-ba kerülnek; a végső topK-vágás (buildResult) dönt.
348
+ // A tag/kind prefilter UGYANAZ, mint a hot-path-é (különben kiszűrt entry-ket hozna vissza a cold-supplement).
349
+ const coldHits = await fam_memory_cold_search_control_service_1.FAM_MemoryColdSearch_ControlService.getInstance().search({
350
+ queryVector: set.queryVector,
351
+ scopeIdSet: set.scopeExpand.scopeIdSet ?? [],
352
+ topK: set.config.topK,
353
+ maxCandidates: set.config.memoryColdSearchMaxCandidates,
354
+ nowMs: Date.now(),
355
+ tagFilter: query.tagFilter,
356
+ kindFilter: query.kindFilter,
357
+ });
358
+ const seen = new Set(hits.map((hit) => hit.id));
359
+ const queryTerms = fam_lexical_match_util_1.FAM_LexicalMatch_Util.tokenize(query.query);
360
+ for (const cold of coldHits) {
361
+ const id = cold.entry._id;
362
+ const weight = cold.entry.weight ?? 1;
363
+ if (!id || seen.has(id) || weight <= 0) {
364
+ continue;
365
+ }
366
+ hits.push(this.toHit({
367
+ entry: cold.entry, vectorHit: { id: id, score: cold.score }, table: set.table, weight: weight, queryTerms: queryTerms,
368
+ importChunkWeight: set.config.importChunkWeight, importSourceWeight: set.config.importSourceWeight,
369
+ includeContent: set.includeContent, config: set.config, nowMs: Date.now(),
370
+ }));
371
+ seen.add(id);
372
+ // KRITIKUS (dsgn-013 §5.3 promóció-gap fix): a cold-talált entry vektorát AZONNAL a hot poolba is upsert-eljük.
373
+ // Különben — miután a reaktiváció `dormant=false`-ra állítja — az entry SEM a hot poolban (csak boot-kor
374
+ // hidratálódik), SEM a cold-keresésben (a `dormant:true` szűrő kizárja) nem lenne megtalálható a következő
375
+ // re-hidratálásig → „elérhetetlen a promóció után". A pool-upsert idempotens; a (ritka) topK-ból kieső,
376
+ // de még dormant entry a következő bootkor úgyis kiesik a poolból (a hidratálás-szűrő korrigálja).
377
+ const vector = (cold.entry.contentVector ?? []);
378
+ if (vector.length) {
379
+ embedding_1.FAM_VectorSearch_ControlService.getInstance().upsertVector({ table: set.table, id: id, vector: vector });
380
+ }
381
+ }
382
+ }
276
383
  /**
277
384
  * Az import-only chunkok (`chunkType:"imports"`) read-idejű rangsor-szorzója (FAM-REV-060). Egy import-sor
278
385
  * (`import { X } from "./y"`) magas cosine-t kap egy szimbólum-név query-re, de alacsony az ÉRTÉKe (csak
@@ -282,18 +389,70 @@ class FAM_Retrieval_ControlService {
282
389
  chunkTypeFactor(chunkType, importChunkWeight) {
283
390
  return chunkType === 'imports' ? importChunkWeight : 1;
284
391
  }
392
+ /**
393
+ * R2 import-source faktor (2026-06-22 eval-lelet): a `source.type:'import'` (claude-mem-import) entry-k finalScore-ja
394
+ * `importSourceWeight`-tel szorzódik (<1), hogy a kurált (`manual`/`agent`/scan) tanulságok a `memory`/`knowledge`
395
+ * táblákon FÖLÉ rangsoroljanak — az import zaja ne temesse őket. Minden NEM-import source érintetlen (×1).
396
+ * READ-time, a nyers `score` (relevancia) változatlan; `importSourceWeight=1` → kikapcsolva (változatlan rangsor).
397
+ */
398
+ importSourceFactor(source, importSourceWeight) {
399
+ return source?.type === 'import' ? importSourceWeight : 1;
400
+ }
401
+ /**
402
+ * Memory Activation Model decay-szorzó (dsgn-013 §3) — CSAK a `memory` táron, ha engedélyezett. A frissen/gyakran
403
+ * FELIDÉZETT memória felül-, a régóta érintetlen alul-rangsorol, de a `floorWeight > 0` miatt SOSEM esik ki. A
404
+ * `lastRecalledAt` hiányában a `__created` ideje a fallback (cold-start kegyelmi idő). Minden más tár → ×1.
405
+ */
406
+ memoryDecayFactor(entry, table, config, nowMs) {
407
+ if (table !== fam_table_type_enum_1.FAM_Table.memory) {
408
+ return 1;
409
+ }
410
+ let factor = 1;
411
+ if (config.memoryActivationEnabled) {
412
+ const createdMs = entry.__created ? new Date(entry.__created).getTime() : undefined;
413
+ factor = fam_memory_activation_util_1.FAM_MemoryActivation_Util.decayFactor({
414
+ lastRecalledAt: entry.lastRecalledAt,
415
+ recallCount: entry.recallCount,
416
+ nowMs: nowMs,
417
+ fallbackMs: createdMs,
418
+ config: {
419
+ halfLifeDays: config.memoryHalfLifeDays, floorWeight: config.memoryFloorWeight,
420
+ maxFrequencyBoost: config.memoryMaxFrequencyBoost, activationWeight: config.memoryActivationWeight,
421
+ },
422
+ });
423
+ }
424
+ // dsgn-013 §6 (MAM F4): a LEVÁLTOTT (superseded) régi tartalom penalizálva — a friss verzió fölébe kerül (de
425
+ // megmarad, cold-kereshető). A penalty az aktiváció-decay MELLÉ szorzódik (akkor is hat, ha a decay ki van kapcsolva).
426
+ if (entry.superseded) {
427
+ factor *= config.memorySupersededPenalty;
428
+ }
429
+ return factor;
430
+ }
285
431
  /** Egy candidate-entry + a vektor-hit → `FAM_RetrievalHit` (finalScore + denormalizált mezők). */
286
432
  toHit(set) {
287
433
  const lastModified = set.entry.__lastModified;
288
- // ⑤ weight-szorzó + FAM-REV-060 import-chunk read-faktor: finalScore = score × weight × chunkTypeFactor.
434
+ // ⑤ weight-szorzó + FAM-REV-060 import-chunk read-faktor + R2 import-SOURCE faktor (2026-06-22 eval-lelet:
435
+ // a claude-mem-import zaja ne temesse a kurált tanulságokat) + dsgn-013 memory-decay (CSAK a memory táron) +
436
+ // Improvement 2 hibrid LEXIKAI keyword-boost (a literal-match felszínre kerül, BOOST-only) →
437
+ // finalScore = score × weight × chunkF × sourceF × decayF × (1 + keywordWeight × lexicalScore).
289
438
  const chunkFactor = this.chunkTypeFactor(set.entry.chunkType, set.importChunkWeight);
439
+ const sourceFactor = this.importSourceFactor(set.entry.source, set.importSourceWeight);
440
+ const decayFactor = this.memoryDecayFactor(set.entry, set.table, set.config, set.nowMs);
441
+ const lexicalScore = set.config.keywordWeight > 0
442
+ ? fam_lexical_match_util_1.FAM_LexicalMatch_Util.score(set.queryTerms, {
443
+ content: set.entry.content, headingPath: set.entry.headingPath, sourceFilePath: set.entry.source?.path,
444
+ })
445
+ : 0;
446
+ const keywordBoost = 1 + set.config.keywordWeight * lexicalScore;
290
447
  return {
291
448
  id: set.vectorHit.id,
292
449
  table: set.table,
293
450
  score: set.vectorHit.score,
294
451
  weight: set.weight,
295
- finalScore: set.vectorHit.score * set.weight * chunkFactor,
452
+ finalScore: set.vectorHit.score * set.weight * chunkFactor * sourceFactor * decayFactor * keywordBoost,
453
+ lexicalScore: lexicalScore,
296
454
  content: set.includeContent ? set.entry.content : undefined,
455
+ contentHash: set.entry.contentHash,
297
456
  kind: set.entry.kind,
298
457
  tags: set.entry.tags ?? [],
299
458
  ...this.ruleMeta(set.entry, set.table),
@@ -324,6 +483,13 @@ class FAM_Retrieval_ControlService {
324
483
  category: rule.category,
325
484
  isActive: rule.isActive,
326
485
  applicableScopes: (rule.applicableScopes ?? []),
486
+ // Rules-Management MP-1 mezők a hit-en (read-DTO bővítés) — a hívó/reflex így megkülönbözteti a KURÁLT
487
+ // kanonikus szabályt (ruleScope/importance/userApproved/ruleId) a nyers, scannelt rules-korpusztól.
488
+ ruleScope: rule.ruleScope,
489
+ ruleScopeTopic: rule.ruleScopeTopic,
490
+ importance: rule.importance,
491
+ userApproved: rule.userApproved,
492
+ ruleId: rule.ruleId,
327
493
  };
328
494
  }
329
495
  // =========================================================================
@@ -488,18 +654,44 @@ class FAM_Retrieval_ControlService {
488
654
  const minScore = query.minScore ?? await this.resolveNumber(config, 'read.minScore', ctx, 0);
489
655
  const relevanceFloor = await this.resolveNumber(config, 'read.relevanceFloor', ctx, 0.5);
490
656
  const importChunkWeight = await this.resolveNumber(config, 'read.importChunkWeight', ctx, 0.4);
657
+ const importSourceWeight = await this.resolveNumber(config, 'read.importSourceWeight', ctx, 0.85);
658
+ const keywordWeight = await this.resolveNumber(config, 'read.keywordWeight', ctx, 0.6);
491
659
  const denseResultRatio = await this.resolveNumber(config, 'read.denseResultRatio', ctx, 2.0);
492
660
  const denseResultAbsMin = await this.resolveNumber(config, 'read.denseResultAbsMin', ctx, 25);
493
661
  const scoreOrderValue = (await config.resolve('read.scoreOrder', ctx)).value;
494
662
  const scoreOrder = typeof scoreOrderValue === 'string' ? scoreOrderValue : 'weighted';
663
+ // Memory Activation Model (dsgn-013) — a `memory.*` namespace; a decay CSAK a memory táron alkalmazódik (toHit).
664
+ const memoryActivationValue = (await config.resolve('memory.activationEnabled', ctx)).value;
665
+ const memoryActivationEnabled = memoryActivationValue !== false;
666
+ const memoryHalfLifeDays = await this.resolveNumber(config, 'memory.decayHalfLifeDays', ctx, 90);
667
+ const memoryFloorWeight = await this.resolveNumber(config, 'memory.floorWeight', ctx, 0.05);
668
+ const memoryMaxFrequencyBoost = await this.resolveNumber(config, 'memory.maxFrequencyBoost', ctx, 2.5);
669
+ const memoryActivationWeight = await this.resolveNumber(config, 'memory.activationWeight', ctx, 0.35);
670
+ const memoryReactivateValue = (await config.resolve('memory.reactivateOnRead', ctx)).value;
671
+ const memoryReactivateOnRead = memoryReactivateValue !== false;
672
+ const memoryColdSearchValue = (await config.resolve('memory.coldSearchEnabled', ctx)).value;
673
+ const memoryColdSearchEnabled = memoryColdSearchValue !== false;
674
+ const memoryColdSearchMaxCandidates = await this.resolveNumber(config, 'memory.coldSearchMaxCandidates', ctx, 5000);
675
+ const memorySupersededPenalty = await this.resolveNumber(config, 'memory.supersededPenalty', ctx, 0.25);
495
676
  return {
496
677
  topK: Math.min(topKDefault, MAX_PAGE_SIZE),
497
678
  minScore: minScore,
498
679
  relevanceFloor: relevanceFloor,
499
680
  importChunkWeight: importChunkWeight,
681
+ importSourceWeight: importSourceWeight,
682
+ keywordWeight: keywordWeight,
500
683
  denseResultRatio: denseResultRatio,
501
684
  denseResultAbsMin: denseResultAbsMin,
502
685
  scoreOrder: scoreOrder,
686
+ memoryActivationEnabled: memoryActivationEnabled,
687
+ memoryHalfLifeDays: memoryHalfLifeDays,
688
+ memoryFloorWeight: memoryFloorWeight,
689
+ memoryMaxFrequencyBoost: memoryMaxFrequencyBoost,
690
+ memoryActivationWeight: memoryActivationWeight,
691
+ memoryReactivateOnRead: memoryReactivateOnRead,
692
+ memoryColdSearchEnabled: memoryColdSearchEnabled,
693
+ memoryColdSearchMaxCandidates: memoryColdSearchMaxCandidates,
694
+ memorySupersededPenalty: memorySupersededPenalty,
503
695
  };
504
696
  }
505
697
  /** Egy szám-config feloldása a default-tal (a `resolve.value` lehet más típus → fallback). */
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAM_RuleInjection_ControlService = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const mongoose_1 = tslib_1.__importDefault(require("mongoose"));
6
+ const error_codes_const_1 = require("../../../_collections/error-codes.const");
7
+ const fam_error_factory_util_1 = require("../../../_collections/fam-error-factory.util");
8
+ const fam_rules_data_model_1 = require("../../../_models/data-models/fam-rules.data-model");
9
+ const embedding_1 = require("../../embedding");
10
+ const fam_rule_injection_util_1 = require("../_collections/fam-rule-injection.util");
11
+ const fam_rule_propagation_util_1 = require("../_collections/fam-rule-propagation.util");
12
+ const fam_rule_scope_context_util_1 = require("../_collections/fam-rule-scope-context.util");
13
+ /**
14
+ * `FAM_RuleInjection_ControlService` (Rules-Management MP-2) — a SessionStart-injektálás (+ a `read`-reflex) szerver-oldali
15
+ * ASSEMBLY-je: a `cwd`-ből (vagy explicit felülírásból) detektálja a tier-kontextust, prefilterezi a `fam_rules`-t a
16
+ * MOST érvényes TIER-ekre (`FAM_RuleInjection_Util.buildFilter`), `importance`-rangsorolja, és char-budgetbe illeszti
17
+ * a „(…and N more)" záró sorral. A hook így DUMB marad (csak hív + az `additionalContext`-be teszi a blokkot) — a teljes
18
+ * ranking/scope/budget logika ITT, szerver-oldalon, unit-tesztelhetően él (mint a `/api/doctor`).
19
+ */
20
+ class FAM_RuleInjection_ControlService {
21
+ static _instance;
22
+ issuer = 'FAM_RuleInjection_ControlService';
23
+ static getInstance() {
24
+ if (!FAM_RuleInjection_ControlService._instance) {
25
+ FAM_RuleInjection_ControlService._instance = new FAM_RuleInjection_ControlService();
26
+ }
27
+ return FAM_RuleInjection_ControlService._instance;
28
+ }
29
+ /** A MOST érvényes always-apply rule-blokk összeállítása (detektált/felülírt kontextus alapján). */
30
+ async assembleInjection(input) {
31
+ const context = this.resolveContext(input);
32
+ const rules = await this.loadRulesForContext(context);
33
+ const assembled = fam_rule_injection_util_1.FAM_RuleInjection_Util.assemble({
34
+ rules: rules, budgetChars: input.budgetChars,
35
+ });
36
+ return { ...assembled, context: context };
37
+ }
38
+ /**
39
+ * MP-5 — a kontextusra érvényes szabályok GENERÁLT, marker-keretezett `CLAUDE.md`-blokkja (propagáció a FAM-nélküli
40
+ * projekt-agenteknek). Budget NÉLKÜL (minden alkalmazható szabály bekerül), dedup `ruleId`/`derivedFrom` szerint.
41
+ */
42
+ async assemblePropagation(input) {
43
+ const context = this.resolveContext(input);
44
+ const rules = await this.loadRulesForContext(context);
45
+ const block = fam_rule_propagation_util_1.FAM_RulePropagation_Util.wrap(fam_rule_propagation_util_1.FAM_RulePropagation_Util.renderBlock(rules));
46
+ return { block: block, ruleCount: fam_rule_propagation_util_1.FAM_RulePropagation_Util.dedup(rules).length, context: context };
47
+ }
48
+ /** A tier-kontextusra érvényes, aktív `rules`-entry-k betöltése injektálható view-ként (közös SSOT a két assembly-hez). */
49
+ async loadRulesForContext(context) {
50
+ const model = this.rulesModel();
51
+ if (!model) {
52
+ throw fam_error_factory_util_1.FAM_Error_Util.create({
53
+ errorCode: error_codes_const_1.FAM_ERROR_CODES.vecSearch,
54
+ message: 'A `fam_rules` model nincs regisztrálva (boot-előtt?).',
55
+ issuer: this.issuer,
56
+ context: { operation: 'load-rules-for-context' },
57
+ });
58
+ }
59
+ const filter = fam_rule_injection_util_1.FAM_RuleInjection_Util.buildFilter(context);
60
+ // CSAK a rangsorhoz/megjelenítéshez kellő mezők (a vektor NEM kell → könnyű lean-query).
61
+ const docs = await model.find(filter, {
62
+ content: 1, importance: 1, weight: 1, globalRule: 1, category: 1,
63
+ ruleScope: 1, ruleScopeTopic: 1, ruleId: 1, derivedFrom: 1, __lastModified: 1,
64
+ }).lean();
65
+ return docs.map((doc) => this.toInjectable(doc));
66
+ }
67
+ /** Explicit felülírás > `cwd`-detekció. A `projectType`/`topics`/budget mindig az inputból. */
68
+ resolveContext(input) {
69
+ const detected = fam_rule_scope_context_util_1.FAM_RuleScopeContext_Util.detect(input.cwd);
70
+ return {
71
+ isFdp: input.isFdp ?? detected.isFdp,
72
+ inProject: input.inProject ?? detected.inProject,
73
+ projectName: detected.projectName,
74
+ projectRoot: detected.projectRoot,
75
+ projectType: input.projectType ?? detected.projectType,
76
+ topics: input.topics ?? detected.topics,
77
+ };
78
+ }
79
+ /** Egy lean Mongo-doc → `FAM_InjectableRule` view. */
80
+ toInjectable(doc) {
81
+ const lastModified = doc.__lastModified;
82
+ return {
83
+ id: typeof doc._id === 'string' ? doc._id : (doc._id ? String(doc._id) : undefined),
84
+ content: typeof doc.content === 'string' ? doc.content : undefined,
85
+ importance: typeof doc.importance === 'number' ? doc.importance : undefined,
86
+ weight: typeof doc.weight === 'number' ? doc.weight : undefined,
87
+ globalRule: doc.globalRule === true,
88
+ category: typeof doc.category === 'string' ? doc.category : undefined,
89
+ ruleScope: typeof doc.ruleScope === 'string' ? doc.ruleScope : undefined,
90
+ ruleScopeTopic: typeof doc.ruleScopeTopic === 'string' ? doc.ruleScopeTopic : undefined,
91
+ ruleId: typeof doc.ruleId === 'string' ? doc.ruleId : undefined,
92
+ derivedFrom: typeof doc.derivedFrom === 'string' ? doc.derivedFrom : undefined,
93
+ lastModifiedMs: lastModified ? new Date(lastModified).getTime() : 0,
94
+ };
95
+ }
96
+ /** A `fam_rules` mongoose-modellje (a DS-példányosítás regisztrálja). */
97
+ rulesModel() {
98
+ new embedding_1.FAM_Entry_DataService({ dataParams: fam_rules_data_model_1.famRules_dataParams, issuer: this.issuer });
99
+ return mongoose_1.default.models[fam_rules_data_model_1.famRules_dataParams.dataName];
100
+ }
101
+ }
102
+ exports.FAM_RuleInjection_ControlService = FAM_RuleInjection_ControlService;
@@ -10,10 +10,21 @@
10
10
  * opcionális relevance-filter = BACKLOG (NEM épül).
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.FAM_RetrievalSuggestions_Util = exports.FAM_RetrievalCandidate_DataService = exports.FAM_Retrieval_ControlService = void 0;
13
+ exports.FAM_RetrievalSuggestions_Util = exports.FAM_RetrievalCandidate_DataService = exports.FAM_RuleScopeContext_Util = exports.FAM_RulePropagation_Util = exports.FAM_RuleInjection_Util = exports.FAM_RuleInjection_ControlService = exports.FAM_GlobalRule_ControlService = exports.FAM_Retrieval_ControlService = void 0;
14
14
  // SP-5.1/5.2/5.3 — a read-pipeline orchestrátor
15
15
  var fam_retrieval_control_service_1 = require("./_services/fam-retrieval.control-service");
16
16
  Object.defineProperty(exports, "FAM_Retrieval_ControlService", { enumerable: true, get: function () { return fam_retrieval_control_service_1.FAM_Retrieval_ControlService; } });
17
+ var fam_global_rule_control_service_1 = require("./_services/fam-global-rule.control-service");
18
+ Object.defineProperty(exports, "FAM_GlobalRule_ControlService", { enumerable: true, get: function () { return fam_global_rule_control_service_1.FAM_GlobalRule_ControlService; } });
19
+ // Rules-Management MP-2 — always-apply rule-injektálás (SessionStart inject + read-reflex) szerver-oldali assembly
20
+ var fam_rule_injection_control_service_1 = require("./_services/fam-rule-injection.control-service");
21
+ Object.defineProperty(exports, "FAM_RuleInjection_ControlService", { enumerable: true, get: function () { return fam_rule_injection_control_service_1.FAM_RuleInjection_ControlService; } });
22
+ var fam_rule_injection_util_1 = require("./_collections/fam-rule-injection.util");
23
+ Object.defineProperty(exports, "FAM_RuleInjection_Util", { enumerable: true, get: function () { return fam_rule_injection_util_1.FAM_RuleInjection_Util; } });
24
+ var fam_rule_propagation_util_1 = require("./_collections/fam-rule-propagation.util");
25
+ Object.defineProperty(exports, "FAM_RulePropagation_Util", { enumerable: true, get: function () { return fam_rule_propagation_util_1.FAM_RulePropagation_Util; } });
26
+ var fam_rule_scope_context_util_1 = require("./_collections/fam-rule-scope-context.util");
27
+ Object.defineProperty(exports, "FAM_RuleScopeContext_Util", { enumerable: true, get: function () { return fam_rule_scope_context_util_1.FAM_RuleScopeContext_Util; } });
17
28
  // SP-5.1 ① scope + ② tag/kind Mongo-prefilter (a vektor-keresés ELŐTT)
18
29
  var fam_retrieval_candidate_data_service_1 = require("./_services/fam-retrieval-candidate.data-service");
19
30
  Object.defineProperty(exports, "FAM_RetrievalCandidate_DataService", { enumerable: true, get: function () { return fam_retrieval_candidate_data_service_1.FAM_RetrievalCandidate_DataService; } });
@@ -54,5 +54,81 @@ class FAM_ScopeMaintenance_Util {
54
54
  },
55
55
  };
56
56
  }
57
+ /**
58
+ * RESET — egy scope (scan-eredetű) entry-jeinek TÖRLÉS-szűrője (PURE, user-FR 2026-06-24 — recovery
59
+ * egy félre-routolt scan után). A `source.type` default `'scan'` → a manuális/agent/import-SZERZETT
60
+ * (pótolhatatlan) tartalmat SOHA nem törli (csak az újra-szkennelhető scan-chunkokat). Opcionális
61
+ * `sourceRootContains` SUBSTRING-szűrés a `source.root`-on (case-insensitive, literál-escape-elve) — a
62
+ * cross-root szennyezés SEBÉSZI eltávolításához: pl. a `ccap-revisioned` gyökerű entry-k a `ccap-client`
63
+ * scope-ból, a `ccap` saját entry-jeinek MEGTARTÁSÁVAL. `sourceRootContains` nélkül a scope MINDEN
64
+ * scan-entry-je (a teljes reset).
65
+ */
66
+ static buildResetFilter(set) {
67
+ const filter = { 'scopePath.scopeId': set.scopeId };
68
+ const sourceType = (set.sourceType ?? 'scan').trim();
69
+ if (sourceType) {
70
+ filter['source.type'] = sourceType;
71
+ }
72
+ const needle = (set.sourceRootContains ?? '').trim();
73
+ if (needle) {
74
+ filter['source.root'] = { $regex: FAM_ScopeMaintenance_Util.escapeRegex(needle), $options: 'i' };
75
+ }
76
+ return filter;
77
+ }
78
+ /** Regex-metakarakter escape (PURE): a substring-szűrőt LITERÁLKÉNT illeszti (nincs véletlen regex-tágítás). */
79
+ static escapeRegex(value) {
80
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
81
+ }
82
+ /**
83
+ * `buildMergeSuggestions(groups)` (scope-prevenció, user-FR 2026-06-22) — TISZTA logika: a detektált
84
+ * duplikátum-csoportokból konkrét, futtatható `merge_scopes from→into` direktívák. A csoportonkénti `into`
85
+ * a **kanonikus** (megtartandó) scope: (1) preferáltan a parented (= tipikusan scan-eredetű, org-fán lóg),
86
+ * (2) azon belül/különben a LEGTÖBB entry-jű (a legtöbb tudást hordozó). A többi tag → `from` (átpontozandó).
87
+ *
88
+ * **Prioritás:** a `'repo-key'` csoportok ELŐBB (ez a legbiztosabb identitás — a KÜLÖNBÖZŐ nevű, AZONOS repo
89
+ * eseteket is elkapja), majd a `'name'` csoportok. Egy `from` scope CSAK EGYSZER javasolt (a már elköltött
90
+ * scope-okat — akár from, akár egy korábbi into — kihagyjuk → nincs ütköző/körkörös merge). Determinisztikus
91
+ * (entryCount-, majd scopeId-rendezés). NEM mutál — csak javaslat; az alkalmazás a `mergeScopes` (dryRun).
92
+ */
93
+ static buildMergeSuggestions(groups) {
94
+ const suggestions = [];
95
+ const consumed = new Set();
96
+ // A 'repo-key' csoportok előbb (biztosabb identitás), majd a 'name'.
97
+ const ordered = groups.slice().sort((a, b) => (a.groupKind === b.groupKind ? 0 : (a.groupKind === 'repo-key' ? -1 : 1)));
98
+ for (const group of ordered) {
99
+ const available = group.members.filter((member) => !consumed.has(member.scopeId));
100
+ if (available.length < 2) {
101
+ continue;
102
+ }
103
+ const into = FAM_ScopeMaintenance_Util.chooseMergeInto(available);
104
+ consumed.add(into.scopeId);
105
+ for (const member of available) {
106
+ if (member.scopeId === into.scopeId || consumed.has(member.scopeId)) {
107
+ continue;
108
+ }
109
+ consumed.add(member.scopeId);
110
+ suggestions.push({
111
+ from: { scopeId: member.scopeId, canonicalName: member.canonicalName },
112
+ into: { scopeId: into.scopeId, canonicalName: into.canonicalName },
113
+ reason: group.groupKind === 'repo-key'
114
+ ? `Azonos kanonikus repo-kulcs ('${group.key}') — különböző nevű scope-ok ugyanarra a repóra mutatnak.`
115
+ : `Azonos (layer|név) duplikátum ('${group.key}') — a parented/nagyobb scope a megtartandó.`,
116
+ fromEntryCount: member.entryCount,
117
+ intoEntryCount: into.entryCount,
118
+ });
119
+ }
120
+ }
121
+ return suggestions;
122
+ }
123
+ /**
124
+ * A megtartandó (`into`) scope kiválasztása egy duplikátum-csoportból (PURE): preferáltan a parented (van
125
+ * `parentScopeId` — az org-fán lógó, tipikusan scan-eredetű), azon belül/különben a LEGTÖBB entry-jű; döntetlen
126
+ * → a kisebb `scopeId` (determinizmus). A csoportnak legalább 1 tagja kell legyen.
127
+ */
128
+ static chooseMergeInto(members) {
129
+ const parented = members.filter((member) => !!member.parentScopeId);
130
+ const pool = parented.length ? parented : members;
131
+ return pool.slice().sort((a, b) => (b.entryCount - a.entryCount) || a.scopeId.localeCompare(b.scopeId))[0];
132
+ }
57
133
  }
58
134
  exports.FAM_ScopeMaintenance_Util = FAM_ScopeMaintenance_Util;