@futdevpro/fdp-agent-memory 1.1.163 → 1.1.177
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 +8 -2
- package/build/src/_cli/_commands/prune-duplicates.command.js +93 -0
- package/build/src/_cli/register-commands.js +2 -0
- package/build/src/_collections/config-catalog.const.js +56 -0
- package/build/src/_models/data-models/fam-entry-base-properties.const.js +1 -0
- package/build/src/_models/data-models/fam-entry.data-model.js +8 -0
- package/build/src/_modules/embedding/_collections/fam-exact-duplicate-prune.util.js +85 -0
- package/build/src/_modules/embedding/_services/fam-entry.data-service.js +21 -0
- package/build/src/_modules/embedding/_services/fam-exact-duplicate-prune.control-service.js +152 -0
- package/build/src/_modules/embedding/index.js +7 -1
- package/build/src/_modules/ingest/_collections/fam-project-config.util.js +101 -0
- package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +23 -1
- package/build/src/_modules/ingest/_services/fam-scan-job.control-service.js +55 -1
- package/build/src/_modules/ingest/_services/fam-scan.control-service.js +14 -0
- package/build/src/_modules/mcp/_collections/fam-core-tools.const.js +6 -0
- package/build/src/_modules/mcp/_services/fam-read-tool.service.js +1 -0
- package/build/src/_modules/retrieval/_collections/fam-cross-table-note.util.js +129 -0
- package/build/src/_modules/retrieval/_services/fam-retrieval.control-service.js +109 -1
- package/build/src/_routes/server/api/api.controller.js +31 -0
- package/package.json +8 -2
- package/build/src/_integration-tests/_helpers/fam-integration-test-setup.util.js +0 -105
package/build/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@futdevpro/fdp-agent-memory",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.177",
|
|
4
4
|
"description": "Local-first, vector-backed multi-table agent memory exposed as an MCP server (read/write/capabilities). Public, FDP-Templates-free, no auth.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -19,7 +19,13 @@
|
|
|
19
19
|
"build",
|
|
20
20
|
"client-dist",
|
|
21
21
|
"README.md",
|
|
22
|
-
"LICENSE"
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"!build/**/*.spec.js",
|
|
24
|
+
"!build/**/*.spec.js.map",
|
|
25
|
+
"!build/**/*.spec.d.ts",
|
|
26
|
+
"!build/**/_benchmarks/**",
|
|
27
|
+
"!build/**/_integration-tests/**",
|
|
28
|
+
"!build/**/_spec-support/**"
|
|
23
29
|
],
|
|
24
30
|
"engines": {
|
|
25
31
|
"node": ">=20"
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_PruneDuplicates_Util = void 0;
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const fam_output_util_1 = require("../_collections/fam-output.util");
|
|
6
|
+
const fam_client_service_1 = require("../_services/fam-client.service");
|
|
7
|
+
const fam_cli_const_1 = require("../_collections/fam-cli.const");
|
|
8
|
+
/**
|
|
9
|
+
* `fam prune-duplicates [--table t] [--apply]` — BÁJTAZONOS ismétlés-nyesés.
|
|
10
|
+
*
|
|
11
|
+
* Az azonos `(absolutePath, chunkIndex, chunkTotal, contentHash)` négyesű példányok között NINCS
|
|
12
|
+
* információ-különbség: a legfrissebbet megtartjuk, a többit soft-delete-eljük, és a csoport
|
|
13
|
+
* `recallCount` / `lastRecalledAt` MAXIMUMÁT átvisszük a megtartottra (a felidézés-történet nem veszhet el).
|
|
14
|
+
*
|
|
15
|
+
* **Alapértelmezés a DRY-RUN** — a törléshez explicit `--apply` kell. A `find-duplicates`-szel NEM
|
|
16
|
+
* keverendő: az SZEMANTIKAI hasonlóságot keres (koszinusz-cluster) és szándékosan sosem töröl.
|
|
17
|
+
*
|
|
18
|
+
* `superseded` bejegyzést tartalmazó csoporthoz SOHA nem nyúl (az a `memory` tár szándékos, leváltott
|
|
19
|
+
* generációja) — a kihagyás számláltan látszik a riportban.
|
|
20
|
+
*
|
|
21
|
+
* ⏱️ **FUTÁSIDŐ (mérve 2026-09-01, 555k aktív chunk):** a TELJES korpusz **~123 s**, egyetlen tár (`rules`)
|
|
22
|
+
* **~1,4 s**. Ez egy hosszan futó, szinkron aggregáció — automatizált hívónál (CI, HTTP-kliens) vagy emeld
|
|
23
|
+
* a timeoutot, vagy `--table`-lel bontsd táranként. A `--max-groups` a MUNKÁT nem, csak a feldolgozott
|
|
24
|
+
* csoportok számát korlátozza (az aggregáció akkor is végigmegy).
|
|
25
|
+
*/
|
|
26
|
+
class FAM_PruneDuplicates_Util {
|
|
27
|
+
/** A `prune-duplicates` parancs-deszkriptor. */
|
|
28
|
+
static command() {
|
|
29
|
+
const command = new commander_1.Command('prune-duplicates');
|
|
30
|
+
command
|
|
31
|
+
.description('Bájtazonos ismétlés-nyesés (a legfrissebb marad; DRY-RUN az alapértelmezés)')
|
|
32
|
+
.option('--table <table>', 'csak ez a tár (default: MINDEN tár)')
|
|
33
|
+
.option('--apply', 'a nyesés TÉNYLEGES végrehajtása (enélkül csak mér, nem ír)')
|
|
34
|
+
.option('--max-groups <n>', 'csoport-plafon egy futásra (default 50000; a capped jelzi, ha több)')
|
|
35
|
+
.action(async () => {
|
|
36
|
+
fam_output_util_1.FAM_Output_Util.exit(await FAM_PruneDuplicates_Util.run(command));
|
|
37
|
+
});
|
|
38
|
+
return command;
|
|
39
|
+
}
|
|
40
|
+
/** A `prune-duplicates` futtatása a REST `POST /api/duplicates/prune`-on. */
|
|
41
|
+
static async run(command) {
|
|
42
|
+
const globals = command.optsWithGlobals();
|
|
43
|
+
const local = command.opts();
|
|
44
|
+
const client = new fam_client_service_1.FAM_CliClient_Service(globals.serverUrl);
|
|
45
|
+
const maxGroups = local.maxGroups !== undefined ? Number(local.maxGroups) : undefined;
|
|
46
|
+
const result = await client.post('/api/duplicates/prune', {
|
|
47
|
+
table: local.table,
|
|
48
|
+
// A `--apply` HIÁNYA a biztonságos irány: dryRun marad. Elgépelt kapcsoló sosem töröl.
|
|
49
|
+
dryRun: local.apply !== true,
|
|
50
|
+
maxGroups: Number.isFinite(maxGroups) ? maxGroups : undefined,
|
|
51
|
+
});
|
|
52
|
+
if (!result.ok) {
|
|
53
|
+
return fam_output_util_1.FAM_Output_Util.failure({
|
|
54
|
+
options: globals,
|
|
55
|
+
command: 'prune-duplicates',
|
|
56
|
+
error: result.error ?? { errorCode: 'FAM-CLI-PRUNE-001', message: 'Ismeretlen nyesési hiba.' },
|
|
57
|
+
exitCode: result.unreachable ? fam_cli_const_1.FAM_CliExitCode.serverUnreachable : fam_cli_const_1.FAM_CliExitCode.operationError,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return fam_output_util_1.FAM_Output_Util.success({
|
|
61
|
+
options: globals,
|
|
62
|
+
command: 'prune-duplicates',
|
|
63
|
+
data: result.data,
|
|
64
|
+
humanFormatter: (data) => FAM_PruneDuplicates_Util.format(data),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/** Ember-olvasható összegzés: per-tár sorok + összesítés + a dry-run explicit jelzése. */
|
|
68
|
+
static format(report) {
|
|
69
|
+
const lines = [];
|
|
70
|
+
lines.push(report.dryRun
|
|
71
|
+
? 'Bájtazonos ismétlés-nyesés — DRY-RUN (semmi nem változott; a végrehajtáshoz: --apply):'
|
|
72
|
+
: 'Bájtazonos ismétlés-nyesés — VÉGREHAJTVA:');
|
|
73
|
+
for (const row of report.tables) {
|
|
74
|
+
if (!row.groups) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
lines.push(` ${row.table.padEnd(18)} ${String(row.groups).padStart(6)} csoport`
|
|
78
|
+
+ ` → ${String(row.prunedChunks).padStart(6)} chunk`
|
|
79
|
+
+ `${row.recallCarried ? `, ${row.recallCarried} felidézés-történet átvive` : ''}`
|
|
80
|
+
+ `${row.skippedSuperseded ? `, ${row.skippedSuperseded} superseded KIHAGYVA` : ''}`
|
|
81
|
+
+ `${row.capped ? ' ⚠ CAPPED (emeld a --max-groups-ot, vagy futtasd újra)' : ''}`);
|
|
82
|
+
}
|
|
83
|
+
if (!report.totalGroups) {
|
|
84
|
+
lines.push(' ✓ nincs bájtazonos ismétlés.');
|
|
85
|
+
return lines.join('\n');
|
|
86
|
+
}
|
|
87
|
+
lines.push(` ÖSSZESEN: ${report.totalGroups} csoport, ${report.totalPrunedChunks} felesleges chunk`
|
|
88
|
+
+ `, ${report.totalRecallCarried} felidézés-történet átvive`
|
|
89
|
+
+ `, ${report.totalSkippedSuperseded} superseded csoport érintetlen.`);
|
|
90
|
+
return lines.join('\n');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.FAM_PruneDuplicates_Util = FAM_PruneDuplicates_Util;
|
|
@@ -8,6 +8,7 @@ const scan_projects_command_1 = require("./_commands/scan-projects.command");
|
|
|
8
8
|
const config_command_1 = require("./_commands/config.command");
|
|
9
9
|
const stats_command_1 = require("./_commands/stats.command");
|
|
10
10
|
const find_duplicates_command_1 = require("./_commands/find-duplicates.command");
|
|
11
|
+
const prune_duplicates_command_1 = require("./_commands/prune-duplicates.command");
|
|
11
12
|
const errors_command_1 = require("./_commands/errors.command");
|
|
12
13
|
const init_command_1 = require("./_commands/init.command");
|
|
13
14
|
const doctor_command_1 = require("./_commands/doctor.command");
|
|
@@ -56,6 +57,7 @@ function registerFAMCommands(program, version) {
|
|
|
56
57
|
remote_command_1.FAM_Remote_Util.command(),
|
|
57
58
|
stats_command_1.FAM_Stats_Util.command(),
|
|
58
59
|
find_duplicates_command_1.FAM_FindDuplicates_Util.command(),
|
|
60
|
+
prune_duplicates_command_1.FAM_PruneDuplicates_Util.command(),
|
|
59
61
|
errors_command_1.FAM_Errors_Util.command(),
|
|
60
62
|
// 7 delegáló alias (README copy-paste-elhetőség, nincs külön logika; dsgn-010 §10).
|
|
61
63
|
doctor_command_1.FAM_Doctor_Util.aliasCommand('validate-env', ['env']),
|
|
@@ -37,6 +37,18 @@ exports.SCAN_IGNORE_PATTERNS_DEFAULT = [
|
|
|
37
37
|
// Generált report-ok / logok / teszt-artefaktumok (FAM-REV-057) — minifikált HTML / nagy JSON-log → embed-hang
|
|
38
38
|
// (pl. dynamo-ngx: 515KB playwright-report HTML, 320KB .dynamo/logs status.json, 173KB logs/cicd-report.json).
|
|
39
39
|
'**/playwright-report/**', '**/test-results/**', '**/.dynamo/**', '**/logs/**', '**/*.log', '**/*-report.json',
|
|
40
|
+
// GENERÁLT audit-/report-/evidence-artefaktumok (2026-08-22, a játék-projektek beemelésekor mérve). A fenti
|
|
41
|
+
// `*-report.json` TÚL SZŰK volt: a VERZIÓZOTT nevek (`asset-qa-report-v14.json`, `cat-animation-audit-v12.json`)
|
|
42
|
+
// átcsúsztak rajta. Mérve a teljes codebase táron: `evidence.json` 1082 chunk / 1,17M char (detektor-kimenet),
|
|
43
|
+
// `*-report*` 788, `*-audit*` 589, `asset|atlas-manifest` 257 (pixel-koordináták, occupancy-arányok),
|
|
44
|
+
// `dom-snapshot` 192, `*-capture` 153.
|
|
45
|
+
'**/*-report*.json', '**/*-audit*.json', '**/evidence.json', '**/dom-snapshot*.json', '**/*-capture.json',
|
|
46
|
+
'**/asset-manifest*.json', '**/atlas-manifest*.json',
|
|
47
|
+
// ⚠️ SZÁNDÉKOSAN NEM `**/reports/**` és NEM `**/manifest.json`:
|
|
48
|
+
// - a `reports/` mappa-minta **4053 markdown DOKSI-chunkot** zárt volna ki (`__documentations/reports/…`),
|
|
49
|
+
// ezért csak az e2e generált riport-mappája megy ki;
|
|
50
|
+
// - a sima `manifest.json` a Unity `Packages/manifest.json` = a FÜGGŐSÉG-LISTA, ami valódi érték.
|
|
51
|
+
'**/e2e/reports/**',
|
|
40
52
|
'**/*.min.js', '**/*.min.css', '**/*.map', '**/.env', '**/.env.*', '**/*.pem', '**/*.key',
|
|
41
53
|
'**/*.secret', '**/secrets/**', '**/*.lock', '**/package-lock.json', '**/pnpm-lock.yaml',
|
|
42
54
|
// Python (L4) — bytecode/venv/teszt-/lint-cache + telepített csomagok (SOHA nem forrás; a `*.pyc`/`*.pyo`
|
|
@@ -50,6 +62,19 @@ exports.SCAN_IGNORE_PATTERNS_DEFAULT = [
|
|
|
50
62
|
// lowercase NEM matchelné a Unity `Logs/`-át).
|
|
51
63
|
'**/Library/**', '**/Temp/**', '**/Obj/**', '**/obj/**', '**/bin/**', '**/Logs/**', '**/Build/**', '**/Builds/**',
|
|
52
64
|
'**/.vs/**', '**/*.csproj', '**/*.sln', '**/*.meta', '**/*.unity', '**/*.prefab', '**/*.asset',
|
|
65
|
+
// Unity GÉPI SZERIALIZÁCIÓ — szöveges fájlok, ezért a bináris-routing ÁTENGEDTE őket, de a tartalmuk
|
|
66
|
+
// GUID-leves / YAML objektum-gráf / animációs görbe / mesh-vertex: NULLA emberi tudás. Mérve (2026-08-22, a
|
|
67
|
+
// játék-projektek beemelése után): **9 017 chunk / 9,5M karakter** csak ezekből — `.shadergraph` 4344,
|
|
68
|
+
// `.dwlt` 1105, `.anim` 1086, `.shadersubgraph` 1075, `.mat` 685, `.vfx` 281, `.controller` 178. És nem is
|
|
69
|
+
// csak holt teher: egy „shader for the water surface" query top-5-jéből **4 slot** ilyen szemét volt, a
|
|
70
|
+
// „UI button click handler"-ből 2 — a grafikai/UI kérdéseket gyakorlatilag használhatatlanná tette.
|
|
71
|
+
// A `.shader`/`.hlsl`/`.compute` SZÁNDÉKOSAN MARAD: azok kézzel írt forrás.
|
|
72
|
+
'**/*.shadergraph', '**/*.shadersubgraph', '**/*.vfx', '**/*.mat', '**/*.physicMaterial', '**/*.physicsMaterial2D',
|
|
73
|
+
'**/*.controller', '**/*.overrideController', '**/*.anim', '**/*.playable', '**/*.signal', '**/*.mask',
|
|
74
|
+
'**/*.terrainlayer', '**/*.brush', '**/*.inputactions', '**/*.lighting', '**/*.giparams', '**/*.cubemap',
|
|
75
|
+
'**/*.flare', '**/*.fontsettings', '**/*.guiskin', '**/*.spriteatlas', '**/*.spriteatlasv2', '**/*.mixer',
|
|
76
|
+
// Unity editor-layout + 3D-mesh + szerkesztői mentés (a `.obj` ProBuilder-generált geometria, nem forrás).
|
|
77
|
+
'**/*.dwlt', '**/*.obj', '**/*.fbx', '**/*.blend', '**/*.bak',
|
|
53
78
|
// Agent-/böngésző-state (lokális futás-melléktermék, SOHA nem korpusz-anyag): `.local-state` (pl. agent
|
|
54
79
|
// browser-automation profil) + Chromium-profil/cache — lockolt SQLite + bináris blobok (Login Data /
|
|
55
80
|
// Cookies / Web Data = SZEMÉLYES adat), olvashatatlanok + junk. (Lelet 2026-06-22: fdp-assistant/__agent/
|
|
@@ -162,6 +187,37 @@ exports.CONFIG_CATALOG = {
|
|
|
162
187
|
+ 'Mert indok (2026-08-22): kontrollalt teszten a rules tar 0/3 aranyban tunt el a documents mogott, '
|
|
163
188
|
+ 'holott a csak-rules query azonnal hozta a kanonikus szabalyt.',
|
|
164
189
|
},
|
|
190
|
+
// KERESZT-TAR "mellesleg"-javaslat (2026-09-01). A `memory`/`knowledge` a HIBAKERESES-jellegu kerdeseken
|
|
191
|
+
// eros ("miert tort el", "lattuk mar ezt"), de az agent jellemzoen `codebase`/`documents`-ben keres, mert
|
|
192
|
+
// nem tudja, hogy ott van. Merve 15 realisztikus kerdesen: 13-nal az ELSODLEGES tar volt jobb -> a talalat
|
|
193
|
+
// NEM kerulhet a listaba (13 esetben rontanank, hogy 2-ben javitsunk), csak kulon, egyetlen mellek-mezobe.
|
|
194
|
+
'read.crossTableNoteEnabled': {
|
|
195
|
+
type: 'boolean', default: true, levels: ALL_LEVELS,
|
|
196
|
+
description: 'Kereszt-tar "mellesleg"-javaslat: ha a memory/knowledge taron ERDEMBEN erosebb talalat van, '
|
|
197
|
+
+ 'a valasz kap egy kulon `crossTableNote` mezot. A `hits` VALTOZATLAN marad (sem sorrend, sem tagsag). '
|
|
198
|
+
+ 'Nem fut, ha a hivo eleve kerte ezeket a tarakat. Merve: +376 ms (+14%) a szonda koltsege.',
|
|
199
|
+
},
|
|
200
|
+
'read.crossTableNoteMinDelta': {
|
|
201
|
+
type: 'number', default: 0.025, min: 0, max: 1, levels: ALL_LEVELS,
|
|
202
|
+
description: 'A megszolalashoz szukseges NYERS score-elony. 0 = kikapcsolva. Kalibralva 44 eseten, '
|
|
203
|
+
+ 'KONTROLLCSOPORTTAL (24 strukturalis kerdes + 20 diagnosztikai). FONTOS: az elso kalibracio '
|
|
204
|
+
+ '0,12-t adott, de kiderult, hogy a magas deltakat szinte kizarolag beszelgetes-tormelek '
|
|
205
|
+
+ '(user_prompt / temporary_context, a memory tar 20,2%-a) termelte. A tormelek kizarasa utan '
|
|
206
|
+
+ '0,12-nel a jegyzet SOHA nem szolalt volna meg. A valos jel a 0,025-0,06 savban van, es ott '
|
|
207
|
+
+ 'pontos: 3/44 megszolalas (6,8%), koztuk a docker-cache-token es a lokal-vs-CI tanulsag. '
|
|
208
|
+
+ 'KORLAT: ket kulonbozo tartalomtipus nyers koszinuszat hasonlitja -> korpusz-valtaskor UJRA KELL MERNI '
|
|
209
|
+
+ '(`node build/src/_benchmarks/fam-crosstable-bench.js`).',
|
|
210
|
+
},
|
|
211
|
+
'read.crossTableNoteMinScore': {
|
|
212
|
+
type: 'number', default: 0.45, min: 0, max: 1, levels: ALL_LEVELS,
|
|
213
|
+
description: 'Abszolut also korlat a jelolt sajat score-jara: gyenge talalatot akkor sem ajanlunk, '
|
|
214
|
+
+ 'ha az elsodleges tar meg gyengebb (kulonben a "nincs talalat" eset minden alkalommal zajt szulne).',
|
|
215
|
+
},
|
|
216
|
+
'read.crossTableNoteTopK': {
|
|
217
|
+
type: 'number', default: 3, min: 1, max: 10, integer: true, levels: ALL_LEVELS,
|
|
218
|
+
description: 'A szonda topK-ja taranként. Kicsi: a szonda csak azt donti el, VAN-E erdemben erosebb — '
|
|
219
|
+
+ 'nem masodik talalati listat epit.',
|
|
220
|
+
},
|
|
165
221
|
'read.relevanceFloor': {
|
|
166
222
|
type: 'number', default: 0.60, min: 0.0, max: 1.0, levels: ALL_LEVELS,
|
|
167
223
|
// PER-TÁR kalibráció (2026-08-14 MÉRÉS): a tárak score-tartománya ELTÉR — a `codebase`/`documents` szimbólum-
|
|
@@ -29,6 +29,7 @@ exports.famEntryBaseProperties = {
|
|
|
29
29
|
referenceLinks: { type: 'string[]', default: [] },
|
|
30
30
|
referenceCodes: { type: 'string[]', index: true, default: [] },
|
|
31
31
|
weight: { type: 'number', default: 1 },
|
|
32
|
+
projectWeight: { type: 'number' },
|
|
32
33
|
importance: { type: 'number', index: true },
|
|
33
34
|
source: { type: 'object' },
|
|
34
35
|
sourceFilePath: { type: 'string', index: true },
|
|
@@ -55,6 +55,14 @@ class FAM_Entry extends fsm_dynamo_1.DyFM_Metadata {
|
|
|
55
55
|
// --- pontozás ---
|
|
56
56
|
/** Retrieval-score multiplikátor (`> 0`); `0` → kizárt. Default `1.0` (per-tár override). */
|
|
57
57
|
weight = 1;
|
|
58
|
+
/**
|
|
59
|
+
* A PROJEKT-szintu retrieval-szorzo a `.famconfig.json`-bol (`FAM_ProjectConfig_Util`). Kulon mezo a
|
|
60
|
+
* `weight`-tol, mert a `weight` a temperalason (`effectiveWeight`) megy at — az `read.weightInfluence: 0.06`
|
|
61
|
+
* mellett onnan MAXIMUM 6% lehuzas johet, barmilyen alacsony erteket adunk (merve 2026-08-22: `weight: 0.6`
|
|
62
|
+
* -> 2,4% csokkenes). Ez a mezo TEMPERALATLANUL szorozza a `finalScore`-t, igy a projekt-szintu policy
|
|
63
|
+
* valodi hatassal bir. Hianyzik/undefined -> 1 (nincs hatas).
|
|
64
|
+
*/
|
|
65
|
+
projectWeight;
|
|
58
66
|
/** Opcionális kvalitatív hangsúly (0..1). */
|
|
59
67
|
importance;
|
|
60
68
|
// --- Memory Activation Model (dsgn-013, CSAK a `memory` táron értelmezett) ---
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PONTOS (bájtazonos) ismétlés-nyesés — a döntési logika, tisztán és tesztelhetően.
|
|
4
|
+
*
|
|
5
|
+
* **Miben más ez, mint a `FAM_DuplicateScan_ControlService`?** Az a SZEMANTIKAI hasonlóságot keresi
|
|
6
|
+
* (koszinusz-cluster) és szándékosan sosem töröl — ott emberi ítélet kell. EZ viszont a BIZONYÍTOTTAN
|
|
7
|
+
* AZONOS újra-beolvasást nyesi: azonos `(source.absolutePath, chunkIndex, chunkTotal, contentHash)` →
|
|
8
|
+
* a példányok között NINCS információ-különbség, így az elhagyásuk nem veszteség.
|
|
9
|
+
*
|
|
10
|
+
* **Mért indok (2026-09-01):** egy projekt-átnevezés után a heti re-scan cél-uniója MINDKÉT nevet
|
|
11
|
+
* megtartotta ugyanarra a mappára, a delta-összevetés pedig a levél-scopeId-re szűrt — így a második
|
|
12
|
+
* menet üres `existing`-et kapott, és mindent `new`-ként vitt be. Az aktív korpuszban ez **13 201**
|
|
13
|
+
* felesleges chunk (a 555 102 aktívból 2,38%). A gyökér-okot a scope-vak fallback + az útvonal-dedup
|
|
14
|
+
* javítja; EZ a maradék takarítása.
|
|
15
|
+
*
|
|
16
|
+
* **Amihez SOHA nem nyúl:**
|
|
17
|
+
* - `superseded` bejegyzés — az a `memory` tár szándékos, leváltott generációja (dsgn-013 MAM F4),
|
|
18
|
+
* az owner explicit kérése szerint MEGMARAD („nem baj ha fennmarad olyan memória amit a Claude már
|
|
19
|
+
* elfelejtett… sőt!"). Mérve: a `memory` táron 0 bájtazonos csoport van, mind a 287 eltérő tartalmú.
|
|
20
|
+
* - ELTÉRŐ `contentHash`-ű példányok — azok valódi tartalom-történet, nem ismétlés (mérve 3 046 csoport).
|
|
21
|
+
*
|
|
22
|
+
* **Megőrzött felidézés-történet:** a csoport `recallCount` / `lastRecalledAt` MAXIMUMA a megtartott
|
|
23
|
+
* példányra vándorol — az owner „amit többet használtunk, az tovább él" elve nem sérülhet a nyesésben.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.FAM_ExactDuplicatePrune_Util = void 0;
|
|
27
|
+
class FAM_ExactDuplicatePrune_Util {
|
|
28
|
+
/**
|
|
29
|
+
* A megtartandó példány: a LEGFRISSEBB `lastModified`. Döntetlennél a lexikálisan legnagyobb `id`
|
|
30
|
+
* nyer — nem esztétika, hanem DETERMINIZMUS: a nyesés kétszer futtatva ugyanazt kell adja, különben
|
|
31
|
+
* a második futás megint mozgatna, és a művelet nem lenne idempotens.
|
|
32
|
+
*/
|
|
33
|
+
static chooseKeeper(members) {
|
|
34
|
+
if (!members.length) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return [...members].sort((a, b) => {
|
|
38
|
+
const at = typeof a.lastModified === 'number' ? a.lastModified : 0;
|
|
39
|
+
const bt = typeof b.lastModified === 'number' ? b.lastModified : 0;
|
|
40
|
+
return bt !== at ? bt - at : (a.id < b.id ? 1 : -1);
|
|
41
|
+
})[0];
|
|
42
|
+
}
|
|
43
|
+
/** A csoport felidézés-történetének maximuma (a megtartottra vitt érték bemenete). */
|
|
44
|
+
static maxRecall(members) {
|
|
45
|
+
let count = 0;
|
|
46
|
+
let at = 0;
|
|
47
|
+
for (const member of members) {
|
|
48
|
+
if (typeof member.recallCount === 'number' && member.recallCount > count) {
|
|
49
|
+
count = member.recallCount;
|
|
50
|
+
}
|
|
51
|
+
if (typeof member.lastRecalledAt === 'number' && member.lastRecalledAt > at) {
|
|
52
|
+
at = member.lastRecalledAt;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { recallCount: count, lastRecalledAt: at };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Egy csoport nyesési terve — vagy a kihagyás INDOKA. A `carry` csak azt a mezőt tartalmazza, ami a
|
|
59
|
+
* megtartotton ténylegesen NŐNE: fölösleges írást nem kérünk, és a keeper saját értékét sem rontjuk le.
|
|
60
|
+
*/
|
|
61
|
+
static planGroup(members) {
|
|
62
|
+
if (members.length < 2) {
|
|
63
|
+
return 'single-member';
|
|
64
|
+
}
|
|
65
|
+
if (members.some((member) => member.superseded === true)) {
|
|
66
|
+
return 'superseded-present';
|
|
67
|
+
}
|
|
68
|
+
const keeper = FAM_ExactDuplicatePrune_Util.chooseKeeper(members);
|
|
69
|
+
const max = FAM_ExactDuplicatePrune_Util.maxRecall(members);
|
|
70
|
+
const carry = {};
|
|
71
|
+
if (max.recallCount > (keeper.recallCount ?? 0)) {
|
|
72
|
+
carry.recallCount = max.recallCount;
|
|
73
|
+
}
|
|
74
|
+
if (max.lastRecalledAt > (keeper.lastRecalledAt ?? 0)) {
|
|
75
|
+
carry.lastRecalledAt = max.lastRecalledAt;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
keepId: keeper.id,
|
|
79
|
+
dropIds: members.filter((member) => member.id !== keeper.id)
|
|
80
|
+
.map((member) => member.id),
|
|
81
|
+
carry: carry,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.FAM_ExactDuplicatePrune_Util = FAM_ExactDuplicatePrune_Util;
|
|
@@ -156,6 +156,27 @@ class FAM_Entry_DataService extends nts_dynamo_1.DyNTS_DataService {
|
|
|
156
156
|
filterBy['source.root'] = { $ne: root };
|
|
157
157
|
await this.updateData({ filterBy: filterBy, update: update });
|
|
158
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* A `scopePath` FELTÉTELES backfill-je (2026-09-01) — a re-scan `equal` ágán hívva.
|
|
161
|
+
*
|
|
162
|
+
* **Miért kell.** A fájl IDENTITÁSA az abszolút útvonal, a scope csak besorolás — ezért az `equal`
|
|
163
|
+
* összevetés (a scope-vak fallback óta) MÁS scope alatt álló chunkot is adoptál. Enélkül a backfill
|
|
164
|
+
* nélkül az adoptált chunk ÖRÖKRE a RÉGI projekt-név alatt maradna: a duplikáció megszűnne, de a
|
|
165
|
+
* scope-szűrt keresés az ÚJ néven nem találná meg. A régi viselkedés (teljes duplikálás) legalább
|
|
166
|
+
* hagyott egy példányt az új scope alatt — a javítás enélkül CSERÉLNE egy hibát egy másikra.
|
|
167
|
+
*
|
|
168
|
+
* Csak akkor ír, ha a LEVÉL scopeId eltér (no-op, ha már stimmel).
|
|
169
|
+
*/
|
|
170
|
+
async backfillScopePath(id, scopePath) {
|
|
171
|
+
if (!scopePath.length) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const leafScopeId = scopePath[scopePath.length - 1].scopeId;
|
|
175
|
+
const update = { $set: { scopePath: scopePath } };
|
|
176
|
+
const filterBy = { _id: id };
|
|
177
|
+
filterBy['scopePath.scopeId'] = { $ne: leafScopeId };
|
|
178
|
+
await this.updateData({ filterBy: filterBy, update: update });
|
|
179
|
+
}
|
|
159
180
|
/**
|
|
160
181
|
* A `sourceFilePath` (display-relatív út) FELTÉTELES backfill-je (FAM-REV-050) — a re-scan `equal` ágán
|
|
161
182
|
* hívva, hogy a default (basePath-nélküli) relatív út KONZISZTENS legyen, miután a fájl-identitás az
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_ExactDuplicatePrune_ControlService = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const mongoose_1 = tslib_1.__importDefault(require("mongoose"));
|
|
6
|
+
const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
|
|
7
|
+
const fam_entry_data_service_1 = require("./fam-entry.data-service");
|
|
8
|
+
const fam_exact_duplicate_prune_util_1 = require("../_collections/fam-exact-duplicate-prune.util");
|
|
9
|
+
const fam_store_registry_const_1 = require("../_collections/fam-store-registry.const");
|
|
10
|
+
const fam_vector_search_control_service_1 = require("./fam-vector-search.control-service");
|
|
11
|
+
const DEFAULT_MAX_GROUPS = 50000;
|
|
12
|
+
/**
|
|
13
|
+
* BÁJTAZONOS ismétlés-nyesés (2026-09-01). Azonos `(absolutePath, chunkIndex, chunkTotal, contentHash)`
|
|
14
|
+
* → a példányok között NINCS információ-különbség; a legfrissebbet tartjuk, a többit soft-delete-eljük.
|
|
15
|
+
*
|
|
16
|
+
* **Nem keverendő** a `FAM_DuplicateScan_ControlService`-szel: az SZEMANTIKAI hasonlóságot keres és
|
|
17
|
+
* sosem töröl (ott emberi ítélet kell). A kritériumok és a kivételek indoklása a pure utilban áll.
|
|
18
|
+
*
|
|
19
|
+
* A soft-delete DIREKT `_deleted` (nem `deleteData`) — a FAM entry-tárak `addArchive:true`-ok, így a
|
|
20
|
+
* per-entry törlés minden példányról ARCHÍV-másolatot csinálna (bloat + lassú tízezres nagyságrenden).
|
|
21
|
+
* Ugyanez a döntés a scope-reset ágon is (`fam-scope-maintenance.control-service.ts`).
|
|
22
|
+
*/
|
|
23
|
+
class FAM_ExactDuplicatePrune_ControlService {
|
|
24
|
+
static _instance;
|
|
25
|
+
static getInstance() {
|
|
26
|
+
if (!FAM_ExactDuplicatePrune_ControlService._instance) {
|
|
27
|
+
FAM_ExactDuplicatePrune_ControlService._instance = new FAM_ExactDuplicatePrune_ControlService();
|
|
28
|
+
}
|
|
29
|
+
return FAM_ExactDuplicatePrune_ControlService._instance;
|
|
30
|
+
}
|
|
31
|
+
/** A nyesés futtatása — `dryRun` esetén CSAK mér, semmit nem ír. */
|
|
32
|
+
async prune(input = {}) {
|
|
33
|
+
const dryRun = input.dryRun !== false;
|
|
34
|
+
const maxGroups = typeof input.maxGroups === 'number' && input.maxGroups > 0
|
|
35
|
+
? Math.floor(input.maxGroups) : DEFAULT_MAX_GROUPS;
|
|
36
|
+
const targets = input.table
|
|
37
|
+
? [fam_store_registry_const_1.FAM_StoreRegistry_Util.getEntry(input.table)].filter(Boolean)
|
|
38
|
+
: fam_store_registry_const_1.FAM_STORE_REGISTRY;
|
|
39
|
+
const tables = [];
|
|
40
|
+
for (const registryEntry of targets) {
|
|
41
|
+
const result = await this.pruneTable(registryEntry, dryRun, maxGroups);
|
|
42
|
+
if (result) {
|
|
43
|
+
tables.push(result);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const sum = (pick) => tables.reduce((acc, row) => acc + pick(row), 0);
|
|
47
|
+
return {
|
|
48
|
+
operation: 'prune_exact_duplicates',
|
|
49
|
+
dryRun: dryRun,
|
|
50
|
+
tables: tables,
|
|
51
|
+
totalGroups: sum((row) => row.groups),
|
|
52
|
+
totalPrunedChunks: sum((row) => row.prunedChunks),
|
|
53
|
+
totalRecallCarried: sum((row) => row.recallCarried),
|
|
54
|
+
totalSkippedSuperseded: sum((row) => row.skippedSuperseded),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Egy tár nyesése. A modell példányosítása regisztrálja a mongoose-modellt (a raw collection eléréséhez). */
|
|
58
|
+
async pruneTable(registryEntry, dryRun, maxGroups) {
|
|
59
|
+
new fam_entry_data_service_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: 'exact-duplicate-prune' });
|
|
60
|
+
const collection = mongoose_1.default.models[registryEntry.dataParams.dataName]?.collection;
|
|
61
|
+
if (!collection) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const groups = await collection.aggregate([
|
|
65
|
+
{
|
|
66
|
+
$match: {
|
|
67
|
+
_deleted: null,
|
|
68
|
+
'source.absolutePath': { $exists: true, $ne: null },
|
|
69
|
+
chunkTotal: { $gt: 0 },
|
|
70
|
+
contentHash: { $exists: true, $ne: null },
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
$group: {
|
|
75
|
+
_id: { path: '$source.absolutePath', index: '$chunkIndex', total: '$chunkTotal', hash: '$contentHash' },
|
|
76
|
+
count: { $sum: 1 },
|
|
77
|
+
members: {
|
|
78
|
+
$push: {
|
|
79
|
+
id: '$_id',
|
|
80
|
+
// A `__lastModified` Date — a tiszta util számot vár, ezért itt konvertáljuk (hiány → null).
|
|
81
|
+
lastModified: { $convert: { input: '$__lastModified', to: 'long', onError: null, onNull: null } },
|
|
82
|
+
recallCount: '$recallCount',
|
|
83
|
+
lastRecalledAt: '$lastRecalledAt',
|
|
84
|
+
superseded: '$superseded',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{ $match: { count: { $gt: 1 } } },
|
|
90
|
+
{ $limit: maxGroups + 1 },
|
|
91
|
+
], { allowDiskUse: true }).toArray();
|
|
92
|
+
const capped = groups.length > maxGroups;
|
|
93
|
+
const work = capped ? groups.slice(0, maxGroups) : groups;
|
|
94
|
+
let prunedChunks = 0;
|
|
95
|
+
let recallCarried = 0;
|
|
96
|
+
let skippedSuperseded = 0;
|
|
97
|
+
for (const group of work) {
|
|
98
|
+
// A nyers `_id`-t megőrizzük (ObjectId VAGY string lehet) — a util string-kulcsot lát, a DB a nyerset kapja.
|
|
99
|
+
const byKey = new Map();
|
|
100
|
+
const members = group.members.map((raw) => {
|
|
101
|
+
const key = String(raw.id);
|
|
102
|
+
byKey.set(key, raw.id);
|
|
103
|
+
return {
|
|
104
|
+
id: key,
|
|
105
|
+
lastModified: typeof raw.lastModified === 'number' ? raw.lastModified : undefined,
|
|
106
|
+
recallCount: raw.recallCount,
|
|
107
|
+
lastRecalledAt: raw.lastRecalledAt,
|
|
108
|
+
superseded: raw.superseded,
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
const plan = fam_exact_duplicate_prune_util_1.FAM_ExactDuplicatePrune_Util.planGroup(members);
|
|
112
|
+
if (typeof plan === 'string') {
|
|
113
|
+
if (plan === 'superseded-present') {
|
|
114
|
+
skippedSuperseded++;
|
|
115
|
+
}
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
prunedChunks += plan.dropIds.length;
|
|
119
|
+
const hasCarry = Object.keys(plan.carry).length > 0;
|
|
120
|
+
if (hasCarry) {
|
|
121
|
+
recallCarried++;
|
|
122
|
+
}
|
|
123
|
+
if (dryRun) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const dropRaw = plan.dropIds.map((id) => byKey.get(id));
|
|
127
|
+
await collection.updateMany({ _id: { $in: dropRaw } }, { $set: { _deleted: new Date() } });
|
|
128
|
+
if (hasCarry) {
|
|
129
|
+
// A felidézés-történet MAXIMUMA a megtartottra — az owner "amit többet használtunk, az tovább
|
|
130
|
+
// él" elve nem sérülhet attól, hogy épp a ritkábban felidézett példány a legfrissebb.
|
|
131
|
+
await collection.updateOne({ _id: byKey.get(plan.keepId) }, { $set: plan.carry });
|
|
132
|
+
}
|
|
133
|
+
for (const id of plan.dropIds) {
|
|
134
|
+
fam_vector_search_control_service_1.FAM_VectorSearch_ControlService.getInstance().removeVector(registryEntry.table, id);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (capped) {
|
|
138
|
+
fsm_dynamo_1.DyFM_Log.warn(`[prune-duplicates] ${registryEntry.table}: a csoport-plafon (${maxGroups}) elerve — `
|
|
139
|
+
+ 'a riport `capped: true`-t ad, tovabbi futas szukseges (nincs nema csonkolas).');
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
table: registryEntry.table,
|
|
143
|
+
collection: collection.collectionName,
|
|
144
|
+
groups: work.length,
|
|
145
|
+
prunedChunks: prunedChunks,
|
|
146
|
+
recallCarried: recallCarried,
|
|
147
|
+
skippedSuperseded: skippedSuperseded,
|
|
148
|
+
capped: capped,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
exports.FAM_ExactDuplicatePrune_ControlService = FAM_ExactDuplicatePrune_ControlService;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* (BFR-AM-002/008); a bedrock-csere (MP-15) a `FAM_EmbeddingProvider` interfész mögött non-breaking.
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.FAM_EmbeddingBootstrap_ControlService = exports.FAM_DupAuditScheduler_ControlService = exports.FAM_DupAudit_DataService = exports.FAM_DupAudit_ControlService = exports.FAM_DupFileRollup_Util = exports.FAM_DupForkPair_Util = exports.FAM_DupCoverage_ControlService = exports.FAM_ExactDuplication_ControlService = exports.FAM_DuplicateScan_ControlService = exports.FAM_EmbeddingCost_ControlService = exports.FAM_EmbeddingPipeline_ControlService = exports.FAM_EmbeddingPreset_ControlService = exports.FAM_StoreRegistry_Util = exports.FAM_STORE_REGISTRY = exports.FAM_DedupWarn_ControlService = exports.FAM_Entry_DataService = exports.FAM_HydrationCoordinator_ControlService = exports.FAM_VectorSearch_ControlService = exports.FAM_MOCK_EMBEDDING_DIMS = exports.FAM_Mock_EmbeddingProvider = exports.FAM_LMStudio_EmbeddingProvider = exports.FAM_OpenAI_EmbeddingProvider = exports.FAM_Embedding_ControlService = void 0;
|
|
11
|
+
exports.FAM_EmbeddingBootstrap_ControlService = exports.FAM_DupAuditScheduler_ControlService = exports.FAM_DupAudit_DataService = exports.FAM_DupAudit_ControlService = exports.FAM_DupFileRollup_Util = exports.FAM_DupForkPair_Util = exports.FAM_DupCoverage_ControlService = exports.FAM_ExactDuplication_ControlService = exports.FAM_DuplicateScan_ControlService = exports.FAM_ExactDuplicatePrune_Util = exports.FAM_ExactDuplicatePrune_ControlService = exports.FAM_EmbeddingCost_ControlService = exports.FAM_EmbeddingPipeline_ControlService = exports.FAM_EmbeddingPreset_ControlService = exports.FAM_StoreRegistry_Util = exports.FAM_STORE_REGISTRY = exports.FAM_DedupWarn_ControlService = exports.FAM_Entry_DataService = exports.FAM_HydrationCoordinator_ControlService = exports.FAM_VectorSearch_ControlService = exports.FAM_MOCK_EMBEDDING_DIMS = exports.FAM_Mock_EmbeddingProvider = exports.FAM_LMStudio_EmbeddingProvider = exports.FAM_OpenAI_EmbeddingProvider = exports.FAM_Embedding_ControlService = void 0;
|
|
12
12
|
// SP-2.1 — provider-dispatch kapu + providerek
|
|
13
13
|
var fam_embedding_control_service_1 = require("./_services/fam-embedding.control-service");
|
|
14
14
|
Object.defineProperty(exports, "FAM_Embedding_ControlService", { enumerable: true, get: function () { return fam_embedding_control_service_1.FAM_Embedding_ControlService; } });
|
|
@@ -39,6 +39,12 @@ var fam_embedding_pipeline_control_service_1 = require("./_services/fam-embeddin
|
|
|
39
39
|
Object.defineProperty(exports, "FAM_EmbeddingPipeline_ControlService", { enumerable: true, get: function () { return fam_embedding_pipeline_control_service_1.FAM_EmbeddingPipeline_ControlService; } });
|
|
40
40
|
var fam_embedding_cost_control_service_1 = require("./_services/fam-embedding-cost.control-service");
|
|
41
41
|
Object.defineProperty(exports, "FAM_EmbeddingCost_ControlService", { enumerable: true, get: function () { return fam_embedding_cost_control_service_1.FAM_EmbeddingCost_ControlService; } });
|
|
42
|
+
// BÁJTAZONOS ismétlés-NYESÉS (2026-09-01) — a szemantikus felderítővel ellentétben EZ töröl, de csak
|
|
43
|
+
// bizonyítottan azonos tartalmat, és `dryRun` az alapértelmezés.
|
|
44
|
+
var fam_exact_duplicate_prune_control_service_1 = require("./_services/fam-exact-duplicate-prune.control-service");
|
|
45
|
+
Object.defineProperty(exports, "FAM_ExactDuplicatePrune_ControlService", { enumerable: true, get: function () { return fam_exact_duplicate_prune_control_service_1.FAM_ExactDuplicatePrune_ControlService; } });
|
|
46
|
+
var fam_exact_duplicate_prune_util_1 = require("./_collections/fam-exact-duplicate-prune.util");
|
|
47
|
+
Object.defineProperty(exports, "FAM_ExactDuplicatePrune_Util", { enumerable: true, get: function () { return fam_exact_duplicate_prune_util_1.FAM_ExactDuplicatePrune_Util; } });
|
|
42
48
|
// near-duplikátum FELDERÍTŐ (read-only; user-direktíva 2026-06-21)
|
|
43
49
|
var fam_duplicate_scan_control_service_1 = require("./_services/fam-duplicate-scan.control-service");
|
|
44
50
|
Object.defineProperty(exports, "FAM_DuplicateScan_ControlService", { enumerable: true, get: function () { return fam_duplicate_scan_control_service_1.FAM_DuplicateScan_ControlService; } });
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_ProjectConfig_Util = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
6
|
+
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
|
|
8
|
+
/**
|
|
9
|
+
* `FAM_ProjectConfig_Util` — a **projektben látható, verziókezelt** FAM-beállítás: `.famconfig.json` a
|
|
10
|
+
* scan-gyökérben, a már bevett `.fdpfamignore` MELLETT (owner-igény 2026-08-22: „ne csak a FAM-ban éljenek
|
|
11
|
+
* ezek a beállítások", legyen kényelmesen konfigurálható és a projektben is látszódjon).
|
|
12
|
+
*
|
|
13
|
+
* **Miért a SÚLY a fő mező.** A retrieval-rangsor RELATÍV: a `finalScore` a jelöltek KÖZÖTT dönt. Ezért egy
|
|
14
|
+
* projekt EGÉSZÉRE adott, egyenletes súly-szorzó pontosan azt csinálja, amit az owner kért:
|
|
15
|
+
* - **általános keresésben** (a projekt más projektekkel VERSENYZIK) a tartalma halkabban jelenik meg;
|
|
16
|
+
* - **célzott keresésben** (`scopeFilter` az adott projektre) MINDEN jelölt ugyanazt a szorzót kapja,
|
|
17
|
+
* így a sorrend VÁLTOZATLAN — a projekt a saját keresésében teljes értékű marad.
|
|
18
|
+
* Nem kell tehát külön „általános vs. célzott" mechanika: az egyenletes szorzó önmagában elvégzi.
|
|
19
|
+
*
|
|
20
|
+
* A `weight: 0` és a negatív érték SZÁNDÉKOSAN tiltott: a `finalScore = score × weight` miatt az a bejegyzést
|
|
21
|
+
* VÉGLEG visszakereshetetlenné tenné (2026-08-22-én mérve a `write` oldalán — az entry a saját egyedi
|
|
22
|
+
* markerére sem jött vissza). Aki ki akar zárni valamit, `.fdpfamignore`-t írjon: az a szándék helyes kifejezése.
|
|
23
|
+
*/
|
|
24
|
+
class FAM_ProjectConfig_Util {
|
|
25
|
+
/** A projekt-konfig fájlneve — a `.fdpfamignore` testvére, ugyanabban a gyökérben. */
|
|
26
|
+
static FILE_NAME = '.famconfig.json';
|
|
27
|
+
/** Catch-all glob a projekt-szintű súlyhoz (a `weights` specifikus mintái ELÉBE kerülnek). */
|
|
28
|
+
static CATCH_ALL = '**';
|
|
29
|
+
/**
|
|
30
|
+
* A `.famconfig.json` beolvasása a megadott gyökérből. Hiányzó fájl → `undefined` (teljesen opcionális).
|
|
31
|
+
* Hibás JSON / érvénytelen érték → **WARN + a hibás mező kihagyása**, SOHA nem dönti meg a scant: egy
|
|
32
|
+
* elgépelt konfig ne akadályozza meg a projekt indexelését (`core-rich-error-handling`).
|
|
33
|
+
*/
|
|
34
|
+
static load(rootDir) {
|
|
35
|
+
const file = path.join(rootDir, FAM_ProjectConfig_Util.FILE_NAME);
|
|
36
|
+
if (!fs.existsSync(file)) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
let raw;
|
|
40
|
+
try {
|
|
41
|
+
raw = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
fsm_dynamo_1.DyFM_Log.warn(`[FAM scan] '${file}' NEM értelmezhető JSON (${error?.message}) — a projekt-konfig `
|
|
45
|
+
+ 'KIHAGYVA, a scan a default beállításokkal folytatódik.');
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
const parsed = (raw && typeof raw === 'object') ? raw : {};
|
|
49
|
+
const config = {};
|
|
50
|
+
const weight = parsed.weight;
|
|
51
|
+
if (weight !== undefined) {
|
|
52
|
+
if (typeof weight === 'number' && Number.isFinite(weight) && weight > 0) {
|
|
53
|
+
config.weight = weight;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
fsm_dynamo_1.DyFM_Log.warn(`[FAM scan] '${file}': a \`weight\` POZITÍV szám kell legyen (kapott: ${String(weight)}) — `
|
|
57
|
+
+ 'kihagyva. A 0/negatív súly visszakereshetetlenné tenné a projektet; kizáráshoz `.fdpfamignore` kell.');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const weights = parsed.weights;
|
|
61
|
+
if (Array.isArray(weights)) {
|
|
62
|
+
const valid = weights.filter((rule) => !!rule && typeof rule === 'object'
|
|
63
|
+
&& typeof rule.pattern === 'string'
|
|
64
|
+
&& typeof rule.weight === 'number'
|
|
65
|
+
&& Number.isFinite(rule.weight)
|
|
66
|
+
&& rule.weight > 0);
|
|
67
|
+
if (valid.length !== weights.length) {
|
|
68
|
+
fsm_dynamo_1.DyFM_Log.warn(`[FAM scan] '${file}': ${weights.length - valid.length} érvénytelen \`weights\` szabály `
|
|
69
|
+
+ 'kihagyva (kell: `pattern` string + POZITÍV `weight`).');
|
|
70
|
+
}
|
|
71
|
+
if (valid.length) {
|
|
72
|
+
config.weights = valid;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (typeof parsed.note === 'string') {
|
|
76
|
+
config.note = parsed.note;
|
|
77
|
+
}
|
|
78
|
+
return (config.weight !== undefined || config.weights || config.note) ? config : undefined;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* A PROJEKT-FAKTOR szabálylistája (a temperálatlan `projectWeight`-hez). Az első illeszkedő nyer, ezért a
|
|
82
|
+
* sorrend jelentéses: ① a konfig SPECIFIKUS mintái, ② a projekt-szintű `weight` catch-all glob-ként — így
|
|
83
|
+
* egy `{"pattern": "__documentations/**", "weight": 1.0}` kivétel TÉNYLEGESEN kiveszi a doksikat a halkítás alól.
|
|
84
|
+
*
|
|
85
|
+
* ⚠️ KÜLÖN CSATORNA a hívó `weights`-étől. A hívó (scan-API) szabályai a `weight` mezőre mennek, ami a
|
|
86
|
+
* retrieval `effectiveWeight` temperálásán át hat (0.06 influence → max 6%). A projekt-konfig szabályai ide
|
|
87
|
+
* jönnek, ahol a szorzó KÖZVETLEN. A kettő összekeverése azt jelentené, hogy a `.famconfig.json`-ban felvett
|
|
88
|
+
* kivétel némán hatástalan marad — pontosan az a fajta látszat-funkció, amit kerülni akarunk.
|
|
89
|
+
*/
|
|
90
|
+
static projectWeightRules(config) {
|
|
91
|
+
if (!config) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
const rules = [...(config.weights ?? [])];
|
|
95
|
+
if (config.weight !== undefined) {
|
|
96
|
+
rules.push({ pattern: FAM_ProjectConfig_Util.CATCH_ALL, weight: config.weight });
|
|
97
|
+
}
|
|
98
|
+
return rules.length ? rules : undefined;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.FAM_ProjectConfig_Util = FAM_ProjectConfig_Util;
|