@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.
- package/build/package.json +1 -1
- package/build/src/_cli/_commands/find-duplicates.command.js +41 -16
- package/build/src/_cli/_commands/scan-projects.command.js +113 -113
- package/build/src/_cli/_commands/scan.command.js +2 -0
- package/build/src/_cli/_commands/serve.command.js +110 -10
- package/build/src/_cli/_services/fam-client.service.js +25 -1
- package/build/src/_cli/register-commands.js +28 -0
- package/build/src/_collections/config-catalog.const.js +148 -6
- package/build/src/_collections/error-codes.const.js +2 -0
- package/build/src/_collections/fam-console.util.js +38 -260
- package/build/src/_collections/fam-db-models.const.js +2 -0
- package/build/src/_collections/fam-entry-bootstrap.util.js +20 -6
- package/build/src/_collections/fam-error-context.util.js +1 -0
- package/build/src/_collections/fam-mcp-bridge.util.js +15 -1
- package/build/src/_collections/fam-operation-queue.service.js +85 -0
- package/build/src/_collections/fam-project-discovery.util.js +148 -0
- package/build/src/_collections/fam-request-origin.util.js +41 -0
- package/build/src/_collections/fam-retry.util.js +48 -0
- package/build/src/_collections/fam-scan-progress-sink.service.js +26 -0
- package/build/src/_enums/fam-rule-scope.type-enum.js +25 -0
- package/build/src/_models/data-models/fam-entry.data-model.js +29 -0
- package/build/src/_models/data-models/fam-memory.data-model.js +5 -0
- package/build/src/_models/data-models/fam-rules.data-model.js +24 -0
- package/build/src/_models/data-models/fam-scan-job.data-model.js +73 -0
- package/build/src/_models/data-models/fam-scope.data-model.js +9 -0
- package/build/src/_modules/embedding/_services/fam-dedup-warn.control-service.js +40 -0
- package/build/src/_modules/embedding/_services/fam-duplicate-scan.control-service.js +74 -16
- package/build/src/_modules/embedding/_services/fam-embedding-bootstrap.control-service.js +50 -0
- package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +31 -5
- package/build/src/_modules/embedding/_services/fam-embedding.control-service.js +142 -9
- package/build/src/_modules/embedding/_services/fam-entry.data-service.js +9 -0
- package/build/src/_modules/embedding/_services/fam-hydration-coordinator.control-service.js +159 -0
- package/build/src/_modules/embedding/_services/fam-lmstudio-embedding.provider.js +41 -4
- package/build/src/_modules/embedding/_services/fam-vector-search.control-service.js +67 -12
- package/build/src/_modules/embedding/index.js +5 -1
- package/build/src/_modules/ingest/_collections/fam-file-routing.util.js +10 -0
- package/build/src/_modules/ingest/_collections/fam-frontmatter.util.js +134 -0
- package/build/src/_modules/ingest/_collections/fam-project-identity.util.js +30 -0
- package/build/src/_modules/ingest/_collections/fam-scan-progress.util.js +5 -2
- package/build/src/_modules/ingest/_collections/fam-scan-summary.util.js +55 -29
- package/build/src/_modules/ingest/_collections/fam-split-chunker.util.js +139 -0
- package/build/src/_modules/ingest/_models/interfaces/fam-scan-job.interface.js +2 -0
- package/build/src/_modules/ingest/_services/fam-chunker.control-service.js +10 -0
- package/build/src/_modules/ingest/_services/fam-delta-compare.util.js +68 -1
- package/build/src/_modules/ingest/_services/fam-ingest-run.data-service.js +1 -1
- package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +173 -37
- package/build/src/_modules/ingest/_services/fam-scan-job.control-service.js +429 -0
- package/build/src/_modules/ingest/_services/fam-scan-job.data-service.js +34 -0
- package/build/src/_modules/ingest/_services/fam-scan.control-service.js +6 -4
- package/build/src/_modules/ingest/index.js +6 -1
- package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +151 -8
- package/build/src/_modules/mcp/_services/fam-read-tool.service.js +70 -0
- package/build/src/_modules/mcp/_services/fam-write-tool.service.js +36 -0
- package/build/src/_modules/retrieval/_collections/fam-lexical-match.util.js +69 -0
- package/build/src/_modules/retrieval/_collections/fam-memory-activation.util.js +66 -0
- package/build/src/_modules/retrieval/_collections/fam-rule-injection.util.js +125 -0
- package/build/src/_modules/retrieval/_collections/fam-rule-propagation.util.js +95 -0
- package/build/src/_modules/retrieval/_collections/fam-rule-scope-context.util.js +74 -0
- package/build/src/_modules/retrieval/_services/fam-global-rule.control-service.js +82 -0
- package/build/src/_modules/retrieval/_services/fam-memory-cold-search.control-service.js +121 -0
- package/build/src/_modules/retrieval/_services/fam-memory-dormancy.control-service.js +99 -0
- package/build/src/_modules/retrieval/_services/fam-memory-reactivation.control-service.js +68 -0
- package/build/src/_modules/retrieval/_services/fam-retrieval-candidate.data-service.js +15 -2
- package/build/src/_modules/retrieval/_services/fam-retrieval.control-service.js +197 -5
- package/build/src/_modules/retrieval/_services/fam-rule-injection.control-service.js +102 -0
- package/build/src/_modules/retrieval/index.js +12 -1
- package/build/src/_modules/scope-reference/_collections/fam-scope-maintenance.util.js +76 -0
- package/build/src/_modules/scope-reference/_services/fam-scope-maintenance.control-service.js +233 -0
- package/build/src/_modules/scope-reference/_services/fam-scope.data-service.js +25 -0
- package/build/src/_routes/server/api/api.controller.js +270 -0
- package/build/src/_routes/server/api/fam-doctor.control-service.js +156 -0
- package/build/src/app.server.js +13 -10
- package/client-dist/{chunk-YXHWCJ5O.js → chunk-GF3FJP7E.js} +71 -71
- package/client-dist/chunk-NMUCMQY6.js +1 -0
- package/client-dist/favicon.ico +0 -0
- package/client-dist/index.html +3 -3
- package/client-dist/main-YK4YIVAQ.js +2 -0
- package/package.json +1 -1
- package/build/src/_cli/_collections/fam-project-discovery.util.js +0 -98
- package/client-dist/chunk-I77GXVAQ.js +0 -1
- package/client-dist/main-PJPEDVJT.js +0 -2
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_HydrationCoordinator_ControlService = void 0;
|
|
4
|
+
const error_codes_const_1 = require("../../../_collections/error-codes.const");
|
|
5
|
+
const fam_error_factory_util_1 = require("../../../_collections/fam-error-factory.util");
|
|
6
|
+
/**
|
|
7
|
+
* `FAM_HydrationCoordinator_ControlService` (Singleton SSOT, user-FR 2026-06-22) — a vektor-pool **hidratálás-
|
|
8
|
+
* készenlét** EGYETLEN koordinátora: a `read`/`search` kérések **MEGVÁRJÁK** egy tár (kezdő VAGY újra-) hidratálását
|
|
9
|
+
* ahelyett, hogy `FAM-VEC-SEARCH-001 pool ÜRES`-t kapnának, amíg a hidratálás folyamatban van.
|
|
10
|
+
*
|
|
11
|
+
* **Probléma (amit megold):** a `/api/health` ELŐBB zöldül, mint ahogy a tárankénti pool-hidratálás (codebase 152k a
|
|
12
|
+
* legnagyobb) befejeződik → közvetlenül boot/auto-start után a `read` `pool ÜRES`-t dobott. A koordinátor **sorba
|
|
13
|
+
* állítja** (queue) a kéréseket: minden várakozó UGYANARRA a per-tár promise-ra vár (nincs polling, nincs thundering-
|
|
14
|
+
* herd) — a hidratálás végén MIND egyszerre továbbenged.
|
|
15
|
+
*
|
|
16
|
+
* **Process-koordináció (SSOT):** a `hydrateAll` boot-kor `beginBoot`-tal ELŐRE `hydrating`-re jelöl MINDEN
|
|
17
|
+
* regisztrált tárat (így a korán beérkező read is vár, akkor is, ha az ő tárának `hydrateTable`-je még nem futott).
|
|
18
|
+
* Minden `hydrateTable` (boot ÉS standalone re-hidratálás egyaránt) `markHydrating`→`markReady`/`markFailed`-et jelez —
|
|
19
|
+
* ezért a **re-hidratálás is fedett**: ha egy `ready` tár újra-hidratál (a pool törlődik + újratölt), a `markHydrating`
|
|
20
|
+
* friss deferred-et nyit → a közben érkező read VÁR, nem lát fél-üres pool-t.
|
|
21
|
+
*
|
|
22
|
+
* **Determinizmus:** a `markReady`/`markFailed` idempotens (ugyanazt a deferred-et nem oldja kétszer); az `awaitReady`
|
|
23
|
+
* a `ready`/`failed` állapotnál azonnal tér vissza/dob, csak a `hydrating` ágon vár (időkorláttal — a time-out tiszta
|
|
24
|
+
* „még hidratál, próbáld újra" hiba, NEM végtelen blokk).
|
|
25
|
+
*/
|
|
26
|
+
class FAM_HydrationCoordinator_ControlService {
|
|
27
|
+
static _instance;
|
|
28
|
+
issuer = 'FAM_HydrationCoordinator_ControlService';
|
|
29
|
+
/** Per-tár állapot + deferred (a SSOT). */
|
|
30
|
+
entries = new Map();
|
|
31
|
+
/** A hidratálás-bevárás default felső korlátja ms-ban (env/config felülírható; lásd `configureWaitMs`). */
|
|
32
|
+
waitTimeoutMs = Number(process.env.FAM_HYDRATION_WAIT_MS) || 180000;
|
|
33
|
+
static getInstance() {
|
|
34
|
+
if (!FAM_HydrationCoordinator_ControlService._instance) {
|
|
35
|
+
FAM_HydrationCoordinator_ControlService._instance = new FAM_HydrationCoordinator_ControlService();
|
|
36
|
+
}
|
|
37
|
+
return FAM_HydrationCoordinator_ControlService._instance;
|
|
38
|
+
}
|
|
39
|
+
/** A hidratálás-bevárás default time-outjának beállítása (a bootstrap a `read.hydrationWaitMs` config-ból hívja). */
|
|
40
|
+
configureWaitMs(waitTimeoutMs) {
|
|
41
|
+
if (Number.isFinite(waitTimeoutMs) && waitTimeoutMs >= 0) {
|
|
42
|
+
this.waitTimeoutMs = Math.floor(waitTimeoutMs);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Az aktuális default time-out (diag / teszt). */
|
|
46
|
+
getWaitTimeoutMs() {
|
|
47
|
+
return this.waitTimeoutMs;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Boot-jelzés (`hydrateAll` hívja a tár-iteráció ELŐTT): MINDEN megadott tárat `hydrating`-re jelöl (friss
|
|
51
|
+
* deferred), hogy a korán beérkező read VÁRJON, akkor is, ha az ő tárának `hydrateTable`-je a soros boot-ban
|
|
52
|
+
* még nem futott le. Idempotens: a már `hydrating` tárat nem nyitja újra.
|
|
53
|
+
*/
|
|
54
|
+
beginBoot(tables) {
|
|
55
|
+
for (const table of tables) {
|
|
56
|
+
this.markHydrating(table);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Egy tár hidratálásának KEZDETE (a `hydrateTable` hívja a pool-törlés ELŐTT — boot ÉS re-hidratálás). Ha a tár még
|
|
61
|
+
* nem `hydrating`, friss deferred-et nyit (a `ready`→`hydrating` átmenet a re-hidratálás kulcsa: az addigi várók a
|
|
62
|
+
* RÉGI, már-feloldott promise-on már átmentek, az újak az ÚJ deferred-re várnak). Ha már `hydrating`, no-op (a
|
|
63
|
+
* `beginBoot`+`hydrateTable` dupla-hívás nem nyit két deferred-et).
|
|
64
|
+
*/
|
|
65
|
+
markHydrating(table) {
|
|
66
|
+
const existing = this.entries.get(table);
|
|
67
|
+
if (existing && existing.state === 'hydrating') {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
let resolve = () => undefined;
|
|
71
|
+
const promise = new Promise((resolver) => {
|
|
72
|
+
resolve = resolver;
|
|
73
|
+
});
|
|
74
|
+
this.entries.set(table, { state: 'hydrating', promise: promise, resolve: resolve });
|
|
75
|
+
}
|
|
76
|
+
/** Egy tár hidratálása SIKERES (a `hydrateTable` hívja a végén): `ready` + a várók továbbengedése. */
|
|
77
|
+
markReady(table) {
|
|
78
|
+
this.settle(table, 'ready', true);
|
|
79
|
+
}
|
|
80
|
+
/** Egy tár hidratálása MEGBUKOTT (a `hydrateTable` catch-ágában): `failed` + a várók elengedése (hiba felé). */
|
|
81
|
+
markFailed(table) {
|
|
82
|
+
this.settle(table, 'failed', false);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Egy tár készenlétének BEVÁRÁSA a `search`/`read` ELŐTT (a queue-mag). `ready` → azonnal vissza; `failed` →
|
|
86
|
+
* tiszta hidratálás-bukás hiba; `hydrating` → a per-tár promise bevárása time-outtal (a time-out NEM végtelen
|
|
87
|
+
* blokk, hanem „még hidratál, próbáld újra"); `pending`/ismeretlen → azonnal vissza (a `search` poolSize-checkje
|
|
88
|
+
* dönt — genuinely üres tár vs. nem-konfigurált). Az időkorlát a `timeoutMs` VAGY a config-default.
|
|
89
|
+
*/
|
|
90
|
+
async awaitReady(table, timeoutMs) {
|
|
91
|
+
const entry = this.entries.get(table);
|
|
92
|
+
if (!entry || entry.state === 'pending') {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (entry.state === 'ready') {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (entry.state === 'failed') {
|
|
99
|
+
throw this.hydrationError(table, 'A tár hidratálása MEGBUKOTT (a pool nem elérhető). '
|
|
100
|
+
+ 'Nézd a szerver-logot (`FAM hydrate` sorok) + az embedding-konfigurációt; majd `re_embed_table`/restart.');
|
|
101
|
+
}
|
|
102
|
+
// state === 'hydrating': a per-tár promise bevárása time-outtal.
|
|
103
|
+
const limitMs = timeoutMs ?? this.waitTimeoutMs;
|
|
104
|
+
const ready = await this.raceWithTimeout(entry.promise, limitMs);
|
|
105
|
+
if (!ready) {
|
|
106
|
+
throw this.hydrationError(table, `A tár hidratálása ${Math.round(limitMs / 1000)}s alatt nem fejeződött be `
|
|
107
|
+
+ '(a pool-hidratálás folyamatban — boot-kor a nagy tárak percekig tarthatnak). Próbáld újra pár '
|
|
108
|
+
+ 'másodperc múlva; a következő hívás már a kész poolt találja.');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** Egy tár aktuális állapota (diag / health / teszt). */
|
|
112
|
+
getState(table) {
|
|
113
|
+
return this.entries.get(table)?.state ?? 'pending';
|
|
114
|
+
}
|
|
115
|
+
/** Pillanatkép minden ismert tár állapotáról (a `/health` + diag bővítéséhez). */
|
|
116
|
+
snapshot() {
|
|
117
|
+
return Array.from(this.entries.entries()).map(([table, entry]) => ({ table: String(table), state: entry.state }));
|
|
118
|
+
}
|
|
119
|
+
// =========================================================================
|
|
120
|
+
// helpers
|
|
121
|
+
// =========================================================================
|
|
122
|
+
/** Az állapot véglegesítése (`ready`/`failed`) + a deferred egyszeri feloldása (idempotens). */
|
|
123
|
+
settle(table, state, ready) {
|
|
124
|
+
const entry = this.entries.get(table);
|
|
125
|
+
if (!entry) {
|
|
126
|
+
// Nem volt `markHydrating` (pl. közvetlen markReady) — rögzítjük a végállapotot egy már-feloldott deferred-del.
|
|
127
|
+
this.entries.set(table, { state: state, promise: Promise.resolve(ready), resolve: () => undefined });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (entry.state === state) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
entry.state = state;
|
|
134
|
+
entry.resolve(ready);
|
|
135
|
+
}
|
|
136
|
+
/** A promise bevárása felső időkorláttal: a time-out `false`-t ad (NEM dob) — a hívó dönt a hibáról. */
|
|
137
|
+
raceWithTimeout(promise, timeoutMs) {
|
|
138
|
+
if (timeoutMs <= 0) {
|
|
139
|
+
return Promise.resolve(false);
|
|
140
|
+
}
|
|
141
|
+
return new Promise((resolve) => {
|
|
142
|
+
const timer = setTimeout(() => resolve(false), timeoutMs);
|
|
143
|
+
void promise.then((ready) => {
|
|
144
|
+
clearTimeout(timer);
|
|
145
|
+
resolve(ready);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/** A hidratálás-bevárás strukturált hibája (`FAM-VEC-HYDRATE-002`). */
|
|
150
|
+
hydrationError(table, detail) {
|
|
151
|
+
return fam_error_factory_util_1.FAM_Error_Util.create({
|
|
152
|
+
errorCode: error_codes_const_1.FAM_ERROR_CODES.vecHydrateWait,
|
|
153
|
+
message: `A(z) '${table}' tár nem áll készen a kereséshez. ${detail}`,
|
|
154
|
+
issuer: this.issuer,
|
|
155
|
+
context: { operation: 'await-hydration', table: table },
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
exports.FAM_HydrationCoordinator_ControlService = FAM_HydrationCoordinator_ControlService;
|
|
@@ -14,18 +14,26 @@ exports.FAM_LMStudio_EmbeddingProvider = void 0;
|
|
|
14
14
|
class FAM_LMStudio_EmbeddingProvider {
|
|
15
15
|
baseUrl;
|
|
16
16
|
apiKey;
|
|
17
|
+
timeoutMs;
|
|
17
18
|
providerId = 'lmstudio';
|
|
19
|
+
/** A request-timeout default (ms), ha a hívó nem ad explicitet — 2 perc (a nagy batch-eknek elég, de BOUNDED). */
|
|
20
|
+
static DEFAULT_TIMEOUT_MS = 120000;
|
|
18
21
|
/**
|
|
19
22
|
* @param baseUrl OpenAI-kompatibilis lokális endpoint (pl. `http://localhost:1234/v1`).
|
|
20
23
|
* @param apiKey opcionális Bearer token (LM Studio default-ban nem kér).
|
|
24
|
+
* @param timeoutMs request-timeout (ms) — egy NEM-VÁLASZOLÓ LM Studio (a modell crash-elt a kérés közben:
|
|
25
|
+
* „bekap egy feladatot, aztán nem csinál semmit") ne fagyassza be a teljes scant. `0`/negatív → nincs timeout
|
|
26
|
+
* (a régi viselkedés). A timeout AbortError-rá fordul → a `FAM_Embedding_ControlService` retry-zza, majd
|
|
27
|
+
* `FAM-EMB-PROVIDER-002`-ként tárolja (user-FR 2026-06-24).
|
|
21
28
|
*/
|
|
22
|
-
constructor(baseUrl, apiKey) {
|
|
29
|
+
constructor(baseUrl, apiKey, timeoutMs = FAM_LMStudio_EmbeddingProvider.DEFAULT_TIMEOUT_MS) {
|
|
23
30
|
this.baseUrl = baseUrl;
|
|
24
31
|
this.apiKey = apiKey;
|
|
32
|
+
this.timeoutMs = timeoutMs;
|
|
25
33
|
}
|
|
26
34
|
/**
|
|
27
35
|
* Batch-embedding az OpenAI-kompatibilis `/embeddings` végpontra. A `texts` sorrendje == a
|
|
28
|
-
* visszaadott `number[][]` sorrendje. HTTP-/parse-hiba deskriptív üzenettel dobódik (a
|
|
36
|
+
* visszaadott `number[][]` sorrendje. HTTP-/parse-/timeout-hiba deskriptív üzenettel dobódik (a
|
|
29
37
|
* `FAM_Embedding_ControlService` `FAM-EMB-PROVIDER-002`-vé alakítja — dsgn-008).
|
|
30
38
|
*/
|
|
31
39
|
async embedTexts(set) {
|
|
@@ -34,11 +42,11 @@ class FAM_LMStudio_EmbeddingProvider {
|
|
|
34
42
|
if (this.apiKey && this.apiKey.trim().length) {
|
|
35
43
|
headers.Authorization = `Bearer ${this.apiKey.trim()}`;
|
|
36
44
|
}
|
|
37
|
-
const response = await
|
|
45
|
+
const response = await this.fetchWithTimeout(url, {
|
|
38
46
|
method: 'POST',
|
|
39
47
|
headers: headers,
|
|
40
48
|
body: JSON.stringify({ model: set.model, input: set.texts }),
|
|
41
|
-
});
|
|
49
|
+
}, set.model);
|
|
42
50
|
const rawText = await response.text();
|
|
43
51
|
if (!response.ok) {
|
|
44
52
|
throw new Error(`LM Studio (OpenAI-compatible) embeddings HTTP ${response.status} | url=${url} | `
|
|
@@ -68,6 +76,35 @@ class FAM_LMStudio_EmbeddingProvider {
|
|
|
68
76
|
return false;
|
|
69
77
|
}
|
|
70
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* `fetch` request-timeout-tal (AbortController) — a KULCS-robusztosság (user-FR 2026-06-24): ha az LM Studio
|
|
81
|
+
* elfogadja a kérést, de SOHA nem válaszol (a modell crash-elt menet közben), a natív `fetch` a végtelenségig
|
|
82
|
+
* függne → a scan-job örökre beragad. A timeout abortálja a kérést → deskriptív hiba, amit a hívó retry-zhat +
|
|
83
|
+
* `FAM-EMB-PROVIDER-002`-ként tárolhat. `timeoutMs<=0` → nincs timeout (régi viselkedés). A timer minden ágon
|
|
84
|
+
* törlődik (a `finally`-ben), hogy ne tartsa életben az event-loop-ot.
|
|
85
|
+
*/
|
|
86
|
+
async fetchWithTimeout(url, init, model) {
|
|
87
|
+
if (this.timeoutMs <= 0) {
|
|
88
|
+
return fetch(url, init);
|
|
89
|
+
}
|
|
90
|
+
const controller = new AbortController();
|
|
91
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
92
|
+
try {
|
|
93
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
// Az AbortController-abort → deskriptív timeout-hiba (a generikus `fetch failed`/`AbortError` helyett),
|
|
97
|
+
// hogy a tárolt rekord egyértelmű legyen: az LM Studio elfogadta, de nem válaszolt időben.
|
|
98
|
+
if (controller.signal.aborted) {
|
|
99
|
+
throw new Error(`LM Studio embeddings: NINCS válasz ${this.timeoutMs}ms alatt (timeout) — a modell valószínűleg `
|
|
100
|
+
+ `crash-elt/lefagyott a kérés közben | url=${url} | model=${model}`);
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
71
108
|
/** Trailing `/`-ek levágása (kettős slash elkerülése a `/embeddings` join-nál). */
|
|
72
109
|
trimTrailingSlashes(value) {
|
|
73
110
|
return value.replace(/\/+$/, '');
|
|
@@ -3,9 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.FAM_VectorSearch_ControlService = void 0;
|
|
4
4
|
const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
|
|
5
5
|
const local_vector_search_1 = require("@futdevpro/nts-dynamo/local-vector-search");
|
|
6
|
+
const fam_table_type_enum_1 = require("../../../_enums/fam-table.type-enum");
|
|
6
7
|
const error_codes_const_1 = require("../../../_collections/error-codes.const");
|
|
7
8
|
const fam_error_factory_util_1 = require("../../../_collections/fam-error-factory.util");
|
|
8
9
|
const fam_store_registry_const_1 = require("../_collections/fam-store-registry.const");
|
|
10
|
+
const fam_hydration_coordinator_control_service_1 = require("./fam-hydration-coordinator.control-service");
|
|
9
11
|
const fam_embedding_control_service_1 = require("./fam-embedding.control-service");
|
|
10
12
|
const fam_entry_data_service_1 = require("./fam-entry.data-service");
|
|
11
13
|
/**
|
|
@@ -48,9 +50,16 @@ class FAM_VectorSearch_ControlService {
|
|
|
48
50
|
* de a többi tár hidratálása FOLYTATÓDIK (egy tár bukása nem dönti le az egészet).
|
|
49
51
|
*/
|
|
50
52
|
async hydrateAll() {
|
|
53
|
+
const startedAt = Date.now();
|
|
54
|
+
let totalVectors = 0;
|
|
55
|
+
// Readiness-koordinátor (user-FR 2026-06-22): MINDEN tárat ELŐRE `hydrating`-re jelölünk a soros boot-iteráció
|
|
56
|
+
// ELŐTT, így a korán beérkező read VÁR (a per-tár promise-on), nem kap `pool ÜRES`-t, akkor sem, ha az ő
|
|
57
|
+
// tárának `hydrateTable`-je a sorban még nem futott le.
|
|
58
|
+
fam_hydration_coordinator_control_service_1.FAM_HydrationCoordinator_ControlService.getInstance()
|
|
59
|
+
.beginBoot(fam_store_registry_const_1.FAM_STORE_REGISTRY.map((entry) => entry.table));
|
|
51
60
|
for (const entry of fam_store_registry_const_1.FAM_STORE_REGISTRY) {
|
|
52
61
|
try {
|
|
53
|
-
await this.hydrateTable(entry.table);
|
|
62
|
+
totalVectors += await this.hydrateTable(entry.table);
|
|
54
63
|
}
|
|
55
64
|
catch (error) {
|
|
56
65
|
// No-silent-failure: a hidratálás-hiba persistált (a global handler kapja), de a többi
|
|
@@ -58,6 +67,11 @@ class FAM_VectorSearch_ControlService {
|
|
|
58
67
|
fsm_dynamo_1.DyFM_Log.warn(`[FAM hydrate] '${entry.table}' tár hidratálása sikertelen: ${error?.message}`);
|
|
59
68
|
}
|
|
60
69
|
}
|
|
70
|
+
// Összesítő (user-FR 2026-06-22): a boot-memória-lábnyom egy ránézésre — összes vektor + idő + heap/RSS.
|
|
71
|
+
const mem = process.memoryUsage();
|
|
72
|
+
fsm_dynamo_1.DyFM_Log.log(`[FAM hydrate] ÖSSZESEN ${totalVectors} vektor ${fam_store_registry_const_1.FAM_STORE_REGISTRY.length} táron, `
|
|
73
|
+
+ `${Math.round((Date.now() - startedAt) / 1000)}s alatt — heap ${Math.round(mem.heapUsed / 1048576)} MB / `
|
|
74
|
+
+ `RSS ${Math.round(mem.rss / 1048576)} MB.`);
|
|
61
75
|
}
|
|
62
76
|
/**
|
|
63
77
|
* Egy tár pool-jának hidratálása (dsgn-006 §4.1). A `completed`-státuszú, nem-üres-vektorú entry-ket
|
|
@@ -67,13 +81,24 @@ class FAM_VectorSearch_ControlService {
|
|
|
67
81
|
async hydrateTable(table) {
|
|
68
82
|
const registryEntry = fam_store_registry_const_1.FAM_StoreRegistry_Util.getEntry(table);
|
|
69
83
|
if (!registryEntry) {
|
|
70
|
-
return;
|
|
84
|
+
return 0;
|
|
71
85
|
}
|
|
86
|
+
// Readiness-koordinátor: a hidratálás KEZDETE (a pool-törlés ELŐTT) — boot ÉS re-hidratálás egyaránt. A
|
|
87
|
+
// `ready`→`hydrating` átmenet a re-hidratálás kulcsa: a `pool.clearPool()` alatt a beérkező read VÁR (friss
|
|
88
|
+
// deferred), nem lát fél-üres pool-t.
|
|
89
|
+
const coordinator = fam_hydration_coordinator_control_service_1.FAM_HydrationCoordinator_ControlService.getInstance();
|
|
90
|
+
coordinator.markHydrating(table);
|
|
72
91
|
const pool = this.getOrCreatePool(table);
|
|
73
92
|
pool.clearPool();
|
|
74
93
|
this.poolDims.set(table, null);
|
|
75
94
|
const dataService = new fam_entry_data_service_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
|
|
76
95
|
const filter = { embeddingStatus: 'completed' };
|
|
96
|
+
// dsgn-013 §5.2 (MAM F3) — az ALVÓ (`dormant`) memóriák NEM hidratálódnak a hot LVS-poolba (RAM-/boot-spórolás);
|
|
97
|
+
// cold (Mongo) kereséssel elérhetők + megtaláláskor reaktiválódnak. A flag CSAK a memory táron létezik; ha a
|
|
98
|
+
// dormancy ki van kapcsolva, nincs `dormant:true` entry → a szűrő no-op (zéró hatás a többi tárra + a mai bootra).
|
|
99
|
+
if (table === fam_table_type_enum_1.FAM_Table.memory) {
|
|
100
|
+
filter.dormant = { $ne: true };
|
|
101
|
+
}
|
|
77
102
|
let loaded = 0;
|
|
78
103
|
try {
|
|
79
104
|
// PERF: lean + {_id, contentVector}-projekció + STREAMING (cursor) — a hidratálás CSAK ezt a két
|
|
@@ -87,6 +112,8 @@ class FAM_VectorSearch_ControlService {
|
|
|
87
112
|
});
|
|
88
113
|
}
|
|
89
114
|
catch (error) {
|
|
115
|
+
// Readiness-koordinátor: a hidratálás MEGBUKOTT → a várók tiszta hibát kapnak (nem végtelen blokk).
|
|
116
|
+
coordinator.markFailed(table);
|
|
90
117
|
throw fam_error_factory_util_1.FAM_Error_Util.create({
|
|
91
118
|
errorCode: error_codes_const_1.FAM_ERROR_CODES.vecHydrate,
|
|
92
119
|
message: `A(z) '${table}' tár vektor-hidratálása sikertelen a Mongo-olvasásnál.`,
|
|
@@ -96,7 +123,16 @@ class FAM_VectorSearch_ControlService {
|
|
|
96
123
|
});
|
|
97
124
|
}
|
|
98
125
|
this.hydrated.set(table, true);
|
|
99
|
-
|
|
126
|
+
// Readiness-koordinátor: a tár KÉSZ → a várakozó read-ek továbbengedése (mind egyszerre, a per-tár promise-on).
|
|
127
|
+
coordinator.markReady(table);
|
|
128
|
+
// Memória-info táranként (user-FR 2026-06-22): dim + becsült pool-méret (loaded × dim × 8 byte / number[])
|
|
129
|
+
// + a process heap/RSS — boot-kor azonnal látszik a per-tár memória-lábnyom.
|
|
130
|
+
const mem = process.memoryUsage();
|
|
131
|
+
const dim = this.poolDims.get(table) ?? 0;
|
|
132
|
+
const poolMb = Math.round((loaded * dim * 8) / 1048576);
|
|
133
|
+
fsm_dynamo_1.DyFM_Log.log(`[FAM hydrate] '${table}' tár: ${loaded} vektor (dim ${dim}, ~${poolMb} MB pool) — `
|
|
134
|
+
+ `heap ${Math.round(mem.heapUsed / 1048576)} MB / RSS ${Math.round(mem.rss / 1048576)} MB.`);
|
|
135
|
+
return loaded;
|
|
100
136
|
}
|
|
101
137
|
// =========================================================================
|
|
102
138
|
// PERSIST-ON-WRITE upsert (dsgn-006 §3) — a write/scan path hívja mentés után
|
|
@@ -130,6 +166,9 @@ class FAM_VectorSearch_ControlService {
|
|
|
130
166
|
* koszinusz (BFR-AM-007). Üres pool / hiányzó embedding → `FAM-VEC-SEARCH-001` (bootstrap-skip jelzés).
|
|
131
167
|
*/
|
|
132
168
|
async search(input) {
|
|
169
|
+
// Readiness-gate (user-FR 2026-06-22): ha a tár pool-ja épp (kezdő VAGY újra-) hidratál, MEGVÁRJUK a végét a
|
|
170
|
+
// queue-mag promise-án (NEM `pool ÜRES`-t dobunk). Time-out / hidratálás-bukás → tiszta `FAM-VEC-HYDRATE-002`.
|
|
171
|
+
await fam_hydration_coordinator_control_service_1.FAM_HydrationCoordinator_ControlService.getInstance().awaitReady(input.table);
|
|
133
172
|
const queryVector = await this.resolveQueryVector(input);
|
|
134
173
|
if (!queryVector.length) {
|
|
135
174
|
throw fam_error_factory_util_1.FAM_Error_Util.create({
|
|
@@ -143,15 +182,16 @@ class FAM_VectorSearch_ControlService {
|
|
|
143
182
|
const pool = this.getOrCreatePool(input.table);
|
|
144
183
|
const poolSize = pool.size();
|
|
145
184
|
if (!poolSize) {
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
185
|
+
// Ide a readiness-gate UTÁN jutunk: a tár hidratálása BEFEJEZŐDÖTT, mégis üres a pool → a tár GENUINELY
|
|
186
|
+
// üres (friss telepítés / még nincs embeddelt tartalom). Ez LEGITIM állapot, a keresés eredménye
|
|
187
|
+
// triviálisan 0 találat — dsgn-002/dsgn-003: „0-találat → üres hits, NEM hiba". (Korábban itt
|
|
188
|
+
// FAM-VEC-SEARCH-001 dobódott → a `read` HTTP 500-at adott MINDEN üres tárra — friss DB-ben a
|
|
189
|
+
// multi-query/edge read-ek és a UI-search borultak; 2026-07-24 e2e-gyökérok-fix.) A hidratálás-BUKÁS
|
|
190
|
+
// esete ettől FÜGGETLENÜL fedett: azt az awaitReady dobja (FAM-VEC-HYDRATE-002). NEM néma: warn-log +
|
|
191
|
+
// a doctor/`test_vector_search` capability továbbra is explicit `empty-pool` státuszt jelez.
|
|
192
|
+
fsm_dynamo_1.DyFM_Log.warn(`[FAM vectorSearch] '${input.table}' tár: üres pool a hidratálás után (0 completed vektor) `
|
|
193
|
+
+ `→ 0 találat (üres tár = legitim állapot, nem hiba).`);
|
|
194
|
+
return [];
|
|
155
195
|
}
|
|
156
196
|
// Prefilter→vektor (dsgn-006 §4.3): ha van candidate-szűkítés, a koszinusz CSAK a szűkített
|
|
157
197
|
// halmazon fut (a teljes pool helyett egy ideiglenes szűkített pool-on) — kisebb számítás.
|
|
@@ -178,6 +218,21 @@ class FAM_VectorSearch_ControlService {
|
|
|
178
218
|
isHydrated(table) {
|
|
179
219
|
return this.hydrated.get(table) ?? false;
|
|
180
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Hidratálás-összegző (user-FR 2026-06-22): a `/health`-en kiadva, hogy a hívó lássa, kész-e a boot-hidratálás
|
|
223
|
+
* (indításkor időbe telik mind a tárat betölteni). `complete` csak ha MINDEN regisztrált tár pool-ja hidratált.
|
|
224
|
+
*/
|
|
225
|
+
hydrationSummary() {
|
|
226
|
+
const pending = fam_store_registry_const_1.FAM_STORE_REGISTRY
|
|
227
|
+
.filter((entry) => !this.isHydrated(entry.table))
|
|
228
|
+
.map((entry) => String(entry.table));
|
|
229
|
+
return {
|
|
230
|
+
complete: pending.length === 0,
|
|
231
|
+
hydratedTables: fam_store_registry_const_1.FAM_STORE_REGISTRY.length - pending.length,
|
|
232
|
+
totalTables: fam_store_registry_const_1.FAM_STORE_REGISTRY.length,
|
|
233
|
+
pendingTables: pending,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
181
236
|
// =========================================================================
|
|
182
237
|
// helpers
|
|
183
238
|
// =========================================================================
|
|
@@ -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_DuplicateScan_ControlService = exports.FAM_EmbeddingCost_ControlService = exports.FAM_EmbeddingPipeline_ControlService = exports.FAM_EmbeddingPreset_ControlService = exports.FAM_StoreRegistry_Util = exports.FAM_STORE_REGISTRY = exports.FAM_Entry_DataService = 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_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;
|
|
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; } });
|
|
@@ -22,8 +22,12 @@ Object.defineProperty(exports, "FAM_MOCK_EMBEDDING_DIMS", { enumerable: true, ge
|
|
|
22
22
|
// SP-2.2 — Memory Vector Search (Mongo-persist + LVS hidratálás)
|
|
23
23
|
var fam_vector_search_control_service_1 = require("./_services/fam-vector-search.control-service");
|
|
24
24
|
Object.defineProperty(exports, "FAM_VectorSearch_ControlService", { enumerable: true, get: function () { return fam_vector_search_control_service_1.FAM_VectorSearch_ControlService; } });
|
|
25
|
+
var fam_hydration_coordinator_control_service_1 = require("./_services/fam-hydration-coordinator.control-service");
|
|
26
|
+
Object.defineProperty(exports, "FAM_HydrationCoordinator_ControlService", { enumerable: true, get: function () { return fam_hydration_coordinator_control_service_1.FAM_HydrationCoordinator_ControlService; } });
|
|
25
27
|
var fam_entry_data_service_1 = require("./_services/fam-entry.data-service");
|
|
26
28
|
Object.defineProperty(exports, "FAM_Entry_DataService", { enumerable: true, get: function () { return fam_entry_data_service_1.FAM_Entry_DataService; } });
|
|
29
|
+
var fam_dedup_warn_control_service_1 = require("./_services/fam-dedup-warn.control-service");
|
|
30
|
+
Object.defineProperty(exports, "FAM_DedupWarn_ControlService", { enumerable: true, get: function () { return fam_dedup_warn_control_service_1.FAM_DedupWarn_ControlService; } });
|
|
27
31
|
var fam_store_registry_const_1 = require("./_collections/fam-store-registry.const");
|
|
28
32
|
Object.defineProperty(exports, "FAM_STORE_REGISTRY", { enumerable: true, get: function () { return fam_store_registry_const_1.FAM_STORE_REGISTRY; } });
|
|
29
33
|
Object.defineProperty(exports, "FAM_StoreRegistry_Util", { enumerable: true, get: function () { return fam_store_registry_const_1.FAM_StoreRegistry_Util; } });
|
|
@@ -58,6 +58,12 @@ class FAM_FileRouting_Util {
|
|
|
58
58
|
if (FAM_FileRouting_Util.isGeneratedNoise(fileName)) {
|
|
59
59
|
return 'skip';
|
|
60
60
|
}
|
|
61
|
+
// R3 (FAM-REV 2026-06-22, eval-lelet): a `.cursor/rules/*` (és `.cursor/.../rules/*`) KONVENCIÓ-fájlok a
|
|
62
|
+
// `rules` tárba — ezek SZABÁLYOK (naming/import/struktúra/tilalmak), nem általános dokumentumok. Korábban
|
|
63
|
+
// `.mdc`→`documents`-be estek → a `rules`-only query az agent-feedback felé húzott (tool-comparison lelet).
|
|
64
|
+
if (FAM_FileRouting_Util.isCursorRule(relativePath)) {
|
|
65
|
+
return fam_table_type_enum_1.FAM_Table.rules;
|
|
66
|
+
}
|
|
61
67
|
if (FAM_FileRouting_Util.DOCUMENT_EXTENSIONS.includes(extension)) {
|
|
62
68
|
return fam_table_type_enum_1.FAM_Table.documents;
|
|
63
69
|
}
|
|
@@ -68,6 +74,10 @@ class FAM_FileRouting_Util {
|
|
|
68
74
|
// codebase. A bináris-tartalmú álcázott fájlt a scanner null-byte tartalom-ellenőrzése szűri.
|
|
69
75
|
return fam_table_type_enum_1.FAM_Table.codebase;
|
|
70
76
|
}
|
|
77
|
+
/** `.cursor/rules/` alatti konvenció-fájl-e (R3 — a `rules` tárba routing). Normalizált `/`, case-insensitive. */
|
|
78
|
+
static isCursorRule(relativePath) {
|
|
79
|
+
return relativePath.replace(/\\/g, '/').toLowerCase().includes('.cursor/rules/');
|
|
80
|
+
}
|
|
71
81
|
/**
|
|
72
82
|
* Generált / zaj fájl-e (FAM-REV-064): a lock-fájlok (pnpm-lock.yaml / package-lock.json / yarn.lock /
|
|
73
83
|
* `*.lock`), a minifikált bundle-k (`.min.js`/`.min.css`) — ezek hatalmasak + generáltak, NEM info-
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_Frontmatter_Util = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* `FAM_Frontmatter_Util` (MP-3 / F-A) — **pure** fájl-fej `---`-frontmatter parser. Ha a tartalom egy `---` sorral
|
|
6
|
+
* KEZDŐDIK és van záró `---` (a `MAX_HEADER_LINES`-on belül), a köztes `key: value` sorokat ismert irányítókká
|
|
7
|
+
* olvassa, a törzset a záró fence UTÁNról adja vissza. Egyébként NO-OP (`hasFrontmatter:false`, `body===content`) —
|
|
8
|
+
* így a kód-/dok-fájlok (amik nem `---`-rel kezdődnek) viselkedése BYTE-AZONOS marad (zero behavior change).
|
|
9
|
+
*
|
|
10
|
+
* Szándékosan MINIMÁL (nem teljes YAML): skalár `string`/`number`/`boolean` + lista (`[a, b]` flow VAGY vesszős).
|
|
11
|
+
* A `#` komment-sor + üres sor kihagyva. Ismeretlen kulcs → ignorált. Defenzív: záró fence nélkül → nincs frontmatter.
|
|
12
|
+
*/
|
|
13
|
+
class FAM_Frontmatter_Util {
|
|
14
|
+
/** A záró fence-t eddig a sorig keressük (egy `---`-rel kezdődő, de nem-frontmatter fájl ne nyelje el a törzset). */
|
|
15
|
+
static MAX_HEADER_LINES = 200;
|
|
16
|
+
/** Az ISMERT irányító-kulcsok (a többi ignorált). */
|
|
17
|
+
static KNOWN_KEYS = new Set([
|
|
18
|
+
'table', 'splitMode', 'importance', 'userApproved', 'ruleScope', 'ruleScopeTopic',
|
|
19
|
+
'ruleId', 'derivedFrom', 'globalRule', 'category', 'tags',
|
|
20
|
+
]);
|
|
21
|
+
/** A fájl-fej frontmatter parse-je (pure). */
|
|
22
|
+
static parse(content) {
|
|
23
|
+
const empty = { directives: {}, body: content, hasFrontmatter: false };
|
|
24
|
+
if (!content) {
|
|
25
|
+
return empty;
|
|
26
|
+
}
|
|
27
|
+
const lines = content.split(/\r?\n/);
|
|
28
|
+
// A nyitó fence: az ELSŐ sor pontosan `---` (BOM-toleráns).
|
|
29
|
+
const first = lines[0].replace(/^/, '').trim();
|
|
30
|
+
if (first !== '---') {
|
|
31
|
+
return empty;
|
|
32
|
+
}
|
|
33
|
+
const closeIdx = FAM_Frontmatter_Util.findCloseFence(lines);
|
|
34
|
+
if (closeIdx < 0) {
|
|
35
|
+
return empty; // nincs záró fence → nem frontmatter (defenzív; pl. `---` thematic-break a tetején)
|
|
36
|
+
}
|
|
37
|
+
const directives = {};
|
|
38
|
+
for (let i = 1; i < closeIdx; i++) {
|
|
39
|
+
FAM_Frontmatter_Util.applyLine(lines[i], directives);
|
|
40
|
+
}
|
|
41
|
+
// CSAK akkor DIREKTÍVA-blokk (+ vágjuk a törzset), ha ≥1 ISMERT ingest-irányítót tartalmaz. Egy TISZTA
|
|
42
|
+
// metaadat-blokk (pl. a `memory`-fájlok `name`/`description`/`metadata` feje) NEM FAM-direktíva → a content
|
|
43
|
+
// ÉRINTETLEN marad (különben a re-scan elvágná a summary-t az embeddelt tartalomból → rontaná a retrievalt).
|
|
44
|
+
if (!Object.keys(directives).length) {
|
|
45
|
+
return empty;
|
|
46
|
+
}
|
|
47
|
+
// A törzs a záró fence UTÁNi soroktól (a vezető üres sorok levágva).
|
|
48
|
+
const body = lines.slice(closeIdx + 1).join('\n').replace(/^\n+/, '');
|
|
49
|
+
return { directives: directives, body: body, hasFrontmatter: true };
|
|
50
|
+
}
|
|
51
|
+
/** A záró `---` fence sor-indexe (1..MAX_HEADER_LINES), vagy -1. */
|
|
52
|
+
static findCloseFence(lines) {
|
|
53
|
+
const limit = Math.min(lines.length, FAM_Frontmatter_Util.MAX_HEADER_LINES);
|
|
54
|
+
for (let i = 1; i < limit; i++) {
|
|
55
|
+
if (lines[i].trim() === '---') {
|
|
56
|
+
return i;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return -1;
|
|
60
|
+
}
|
|
61
|
+
/** Egy `key: value` sor feldolgozása (ismert kulcs → típusos érték a `directives`-be). */
|
|
62
|
+
static applyLine(line, directives) {
|
|
63
|
+
const trimmed = line.trim();
|
|
64
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const sep = trimmed.indexOf(':');
|
|
68
|
+
if (sep < 0) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const key = trimmed.slice(0, sep).trim();
|
|
72
|
+
if (!FAM_Frontmatter_Util.KNOWN_KEYS.has(key)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const rawValue = FAM_Frontmatter_Util.stripQuotes(trimmed.slice(sep + 1).trim());
|
|
76
|
+
switch (key) {
|
|
77
|
+
case 'table':
|
|
78
|
+
case 'ruleScope':
|
|
79
|
+
case 'ruleScopeTopic':
|
|
80
|
+
case 'ruleId':
|
|
81
|
+
case 'derivedFrom':
|
|
82
|
+
case 'category':
|
|
83
|
+
if (rawValue) {
|
|
84
|
+
directives[key] = rawValue;
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
case 'importance': {
|
|
88
|
+
const num = Number(rawValue);
|
|
89
|
+
if (Number.isFinite(num)) {
|
|
90
|
+
directives.importance = num;
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
case 'userApproved':
|
|
95
|
+
directives.userApproved = FAM_Frontmatter_Util.toBool(rawValue);
|
|
96
|
+
return;
|
|
97
|
+
case 'globalRule':
|
|
98
|
+
directives.globalRule = FAM_Frontmatter_Util.toBool(rawValue);
|
|
99
|
+
return;
|
|
100
|
+
case 'splitMode':
|
|
101
|
+
directives.splitMode = FAM_Frontmatter_Util.toSplitMode(rawValue);
|
|
102
|
+
return;
|
|
103
|
+
case 'tags':
|
|
104
|
+
directives.tags = FAM_Frontmatter_Util.toList(rawValue);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Idéző-jelek levágása (`"x"` / `'x'` → `x`). */
|
|
109
|
+
static stripQuotes(value) {
|
|
110
|
+
if (value.length >= 2 && ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\'')))) {
|
|
111
|
+
return value.slice(1, -1);
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
/** `'true'`/`'yes'`/`'1'` → `true`; minden más → `false`. */
|
|
116
|
+
static toBool(value) {
|
|
117
|
+
const lower = value.toLowerCase();
|
|
118
|
+
return lower === 'true' || lower === 'yes' || lower === '1';
|
|
119
|
+
}
|
|
120
|
+
/** `'heading'`/`'list-item'` → az adott mód; `true`/`'true'`/`'yes'` → `'heading'`; egyébként `false`. */
|
|
121
|
+
static toSplitMode(value) {
|
|
122
|
+
const lower = value.toLowerCase();
|
|
123
|
+
if (lower === 'heading' || lower === 'list-item') {
|
|
124
|
+
return lower;
|
|
125
|
+
}
|
|
126
|
+
return FAM_Frontmatter_Util.toBool(value) ? 'heading' : false;
|
|
127
|
+
}
|
|
128
|
+
/** `[a, b]` flow VAGY `a, b` vesszős → string-lista (üresek kiszűrve). */
|
|
129
|
+
static toList(value) {
|
|
130
|
+
const inner = value.startsWith('[') && value.endsWith(']') ? value.slice(1, -1) : value;
|
|
131
|
+
return inner.split(',').map((entry) => FAM_Frontmatter_Util.stripQuotes(entry.trim())).filter((entry) => entry.length > 0);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
exports.FAM_Frontmatter_Util = FAM_Frontmatter_Util;
|
|
@@ -95,6 +95,36 @@ class FAM_ProjectIdentity_Util {
|
|
|
95
95
|
static hasIdentity(identity) {
|
|
96
96
|
return Boolean(identity.packageName || identity.isGit);
|
|
97
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* `canonicalRepoKey(identity, projectDirName?)` (scope-prevenció, user-FR 2026-06-22; **path-aware fix
|
|
100
|
+
* 2026-06-24**) — a STABIL projekt-identitás-kulcs, amit a SCAN a project-scope-ra stamp-el
|
|
101
|
+
* (`FAM_Scope.canonicalRepoKey`), és amire a duplikátum-detekció épül.
|
|
102
|
+
*
|
|
103
|
+
* **KRITIKUS path-aware fix:** a régi forma CSAK a git `org/repo`-t használta → MONOREPO sub-projektek ÉS
|
|
104
|
+
* forkok (egy git-repóban élő, jogosan KÜLÖNBÖZŐ projektek) UGYANAZT a kulcsot kapták → a duplikátum-detekció
|
|
105
|
+
* tévesen jelezte őket egynek → egy téves `merge_scopes` ÖSSZEKEVERTE a tartalmukat (pl. `ccap-revisioned`
|
|
106
|
+
* 42k fájlja a `ccap-speech-recognition` scope-ba). A javítás: a kulcs `${repo}::${diszkriminátor}`, ahol a
|
|
107
|
+
* diszkriminátor a projekt-azonosító A REPÓN BELÜL (package-név bare alak VAGY a projekt-mappa neve). Így két
|
|
108
|
+
* külön projekt EGY repóban is külön kulcsot kap, miközben UGYANANNAK a projektnek az import-/scan-változata
|
|
109
|
+
* (azonos package-név) továbbra is egy kulcsra esik (a dedup eredeti célja megmarad).
|
|
110
|
+
*
|
|
111
|
+
* Prioritás: (1) git `org/repo` van → `${repo}::${pkgBare || dir}` (path-aware), különben csak `repo`;
|
|
112
|
+
* (2) nincs git → a package-név bare alakja VAGY a mappanév. Minden LOWERCASE + trim. Nincs semmi → `undefined`.
|
|
113
|
+
* PURE — IO nélkül tesztelhető.
|
|
114
|
+
*/
|
|
115
|
+
static canonicalRepoKey(identity, projectDirName) {
|
|
116
|
+
const repo = identity.gitRepo?.trim().toLowerCase();
|
|
117
|
+
const pkgBare = identity.packageName
|
|
118
|
+
? identity.packageName.replace(/^@[^/]+\//, '').trim().toLowerCase()
|
|
119
|
+
: undefined;
|
|
120
|
+
const dir = projectDirName?.trim().toLowerCase() || undefined;
|
|
121
|
+
const discriminator = pkgBare || dir;
|
|
122
|
+
if (repo) {
|
|
123
|
+
// path-aware: a repón belüli projekt-azonosító (ha van) → a monorepo/fork sub-projektek külön kulcsot kapnak.
|
|
124
|
+
return discriminator ? `${repo}::${discriminator}` : repo;
|
|
125
|
+
}
|
|
126
|
+
return discriminator || undefined;
|
|
127
|
+
}
|
|
98
128
|
/** A kereshető `knowledge` „project-identity" entry szövege (PURE). */
|
|
99
129
|
static buildContent(identity, scopeLabel, root) {
|
|
100
130
|
const lines = [];
|
|
@@ -40,7 +40,7 @@ class FAM_ScanProgress_Util {
|
|
|
40
40
|
/**
|
|
41
41
|
* Egy futás-közbeni progress-sor: `processed/total` fájl + %, chunk-szám, verdikt-bontás, eltelt idő + ETA.
|
|
42
42
|
* Az ETA a MÉRT sebességből (`processed / elapsed`) → a hátralévő fájlok ideje (csak ha van haladás + maradék).
|
|
43
|
-
* `[famIngest]` prefix →
|
|
43
|
+
* `[famIngest]` prefix → a szerver látható konzol-ablakában (és a `FAM_LOG_FILE`-ban) megjelenik.
|
|
44
44
|
*/
|
|
45
45
|
static progressLine(set) {
|
|
46
46
|
const pct = set.total ? Math.round((set.processed / set.total) * 100) : 100;
|
|
@@ -49,7 +49,10 @@ class FAM_ScanProgress_Util {
|
|
|
49
49
|
const eta = rate > 0 && remaining > 0
|
|
50
50
|
? `, ETA ~${FAM_ScanProgress_Util.formatDuration(remaining / rate)}`
|
|
51
51
|
: '';
|
|
52
|
-
|
|
52
|
+
// A projekt-/scope-név a sor elejére (user-FR 2026-06-22) — multi-projekt scan-nél a sor magában
|
|
53
|
+
// is megmondja, MELYIK projektnél tart (nem csak a külön project-identity/summary sor).
|
|
54
|
+
const scope = set.scopeLabel ? `[${set.scopeLabel}] ` : '';
|
|
55
|
+
return `[famIngest] ${scope}feldolgozás ${set.processed}/${set.total} fájl (${pct}%), ${set.chunkCount} chunk, `
|
|
53
56
|
+ `${set.verdicts.new} új / ${set.verdicts.modified} mód / ${set.verdicts.equal} vált., `
|
|
54
57
|
+ `eltelt ${FAM_ScanProgress_Util.formatDuration(set.elapsedSeconds)}${eta}`;
|
|
55
58
|
}
|