@futdevpro/fdp-agent-memory 0.1.0 → 1.1.12
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/LICENSE +21 -0
- package/README.md +7 -7
- package/build/package.json +6 -5
- package/build/src/_cli/_collections/fam-arg.util.js +48 -0
- package/build/src/_cli/_collections/fam-cli.const.js +40 -0
- package/build/src/_cli/_collections/fam-output.util.js +86 -0
- package/build/src/_cli/_collections/fam-project-discovery.util.js +98 -0
- package/build/src/_cli/_commands/capture.command.js +73 -0
- package/build/src/_cli/_commands/config.command.js +93 -0
- package/build/src/_cli/_commands/doctor.command.js +124 -0
- package/build/src/_cli/_commands/errors.command.js +66 -0
- package/build/src/_cli/_commands/export.command.js +65 -0
- package/build/src/_cli/_commands/find-duplicates.command.js +97 -0
- package/build/src/_cli/_commands/import.command.js +136 -0
- package/build/src/_cli/_commands/init.command.js +147 -0
- package/build/src/_cli/_commands/read.command.js +109 -0
- package/build/src/_cli/_commands/scan-projects.command.js +138 -0
- package/build/src/_cli/_commands/scan.command.js +98 -0
- package/build/src/_cli/_commands/seed.command.js +40 -0
- package/build/src/_cli/_commands/serve.command.js +350 -0
- package/build/src/_cli/_commands/start.command.js +134 -0
- package/build/src/_cli/_commands/stats.command.js +54 -0
- package/build/src/_cli/_commands/write.command.js +103 -0
- package/build/src/_cli/_models/interfaces/fam-cli-global-options.interface.js +2 -0
- package/build/src/_cli/_models/interfaces/fam-cli-output.interface.js +9 -0
- package/build/src/_cli/_models/interfaces/fam-client-result.interface.js +2 -0
- package/build/src/_cli/_services/fam-client.service.js +140 -0
- package/build/src/_cli/register-commands.js +86 -0
- package/build/src/_collections/config-catalog.const.js +67 -1
- package/build/src/_collections/fam-console.util.js +367 -0
- package/build/src/_collections/fam-entry-bootstrap.util.js +158 -4
- package/build/src/_collections/fam-error-factory.util.js +0 -9
- package/build/src/_collections/fam-mcp-bridge.util.js +49 -0
- package/build/src/_collections/fam-reference-code.util.js +105 -0
- package/build/src/_collections/fam-version.const.js +10 -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 +6 -0
- package/build/src/_models/data-models/fam-ingest-run.data-model.js +3 -1
- package/build/src/_models/data-models/fam-reference.data-model.js +7 -0
- package/build/src/_modules/capture/_collections/fam-capture.const.js +11 -0
- package/build/src/_modules/capture/_services/fam-auto-capture.control-service.js +87 -0
- package/build/src/_modules/capture/index.js +8 -0
- package/build/src/_modules/embedding/_collections/fam-embedding-prefix.util.js +77 -0
- package/build/src/_modules/embedding/_services/fam-duplicate-scan.control-service.js +202 -0
- package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +33 -9
- package/build/src/_modules/embedding/_services/fam-embedding.control-service.js +21 -2
- package/build/src/_modules/embedding/_services/fam-entry.data-service.js +135 -0
- package/build/src/_modules/embedding/_services/fam-vector-search.control-service.js +42 -32
- package/build/src/_modules/embedding/index.js +4 -1
- package/build/src/_modules/export/_collections/fam-export.const.js +22 -0
- package/build/src/_modules/export/_services/fam-export.control-service.js +64 -0
- package/build/src/_modules/export/index.js +8 -0
- package/build/src/_modules/ingest/_collections/fam-famignore.util.js +83 -0
- package/build/src/_modules/ingest/_collections/fam-file-routing.util.js +59 -48
- package/build/src/_modules/ingest/_collections/fam-project-identity.util.js +134 -0
- package/build/src/_modules/ingest/_collections/fam-scan-progress.util.js +57 -0
- package/build/src/_modules/ingest/_collections/fam-scan-summary.util.js +60 -0
- package/build/src/_modules/ingest/_collections/fam-scan-weight.util.js +53 -0
- package/build/src/_modules/ingest/_collections/fam-secret-exclude.util.js +37 -14
- package/build/src/_modules/ingest/_collections/fam-sliding-chunker.util.js +34 -0
- package/build/src/_modules/ingest/_collections/fam-ts-chunker.util.js +200 -14
- package/build/src/_modules/ingest/_services/fam-delta-compare.util.js +4 -1
- package/build/src/_modules/ingest/_services/fam-ingest-run.data-service.js +7 -4
- package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +346 -17
- package/build/src/_modules/ingest/_services/fam-scan.control-service.js +25 -2
- package/build/src/_modules/ingest/index.js +3 -1
- package/build/src/_modules/mcp/_collections/fam-active-rules.util.js +56 -0
- package/build/src/_modules/mcp/_collections/fam-core-tools.const.js +47 -6
- package/build/src/_modules/mcp/_services/fam-capabilities-tool.service.js +4 -4
- package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +224 -18
- package/build/src/_modules/mcp/_services/fam-mcp-adapter.service.js +4 -4
- package/build/src/_modules/mcp/_services/fam-mcp-server.service.js +4 -4
- package/build/src/_modules/mcp/_services/fam-read-tool.service.js +53 -1
- package/build/src/_modules/mcp/_services/fam-write-tool.service.js +104 -8
- package/build/src/_modules/mcp/index.js +4 -4
- package/build/src/_modules/migration/_collections/fam-claude-mem-normalize.util.js +66 -3
- package/build/src/_modules/migration/_collections/fam-prompt-aggregate.util.js +143 -0
- package/build/src/_modules/migration/_collections/fam-target-mapping.util.js +19 -0
- package/build/src/_modules/migration/_enums/fam-claude-mem-source.type-enum.js +6 -0
- package/build/src/_modules/migration/_models/interfaces/fam-claude-mem.interface.js +5 -0
- package/build/src/_modules/migration/_services/fam-agent-memory-reader.service.js +125 -0
- package/build/src/_modules/migration/_services/fam-claude-mem-import.control-service.js +101 -18
- package/build/src/_modules/migration/_services/fam-import-dedup.data-service.js +53 -0
- package/build/src/_modules/migration/index.js +3 -1
- package/build/src/_modules/retrieval/_services/fam-retrieval-candidate.data-service.js +78 -4
- package/build/src/_modules/retrieval/_services/fam-retrieval.control-service.js +293 -50
- package/build/src/_modules/scope-reference/_collections/fam-scope-normalize.util.js +6 -3
- package/build/src/_modules/scope-reference/_services/fam-reference.data-service.js +18 -0
- package/build/src/_modules/scope-reference/_services/fam-scope-resolver.control-service.js +79 -20
- package/build/src/_routes/server/api/api.controller.js +34 -2
- package/build/src/_routes/server/client-app/client-app.control-service.js +1 -1
- package/build/src/_routes/server/server-status/server-status.controller.js +2 -1
- package/build/src/app.server.js +13 -1
- package/build/src/environments/environment.js +1 -1
- package/build/src/index.js +1 -1
- package/client-dist/{chunk-GHKRM4SM.js → chunk-I77GXVAQ.js} +1 -1
- package/client-dist/{chunk-LMTL7GA3.js → chunk-YXHWCJ5O.js} +1 -1
- package/client-dist/index.html +1 -1
- package/client-dist/{main-2KWB3QYK.js → main-PJPEDVJT.js} +1 -1
- package/package.json +6 -5
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FAM_Retrieval_ControlService = void 0;
|
|
4
|
+
const fam_table_type_enum_1 = require("../../../_enums/fam-table.type-enum");
|
|
5
|
+
const fam_reference_code_util_1 = require("../../../_collections/fam-reference-code.util");
|
|
4
6
|
const config_control_service_1 = require("../../../_routes/server/config/config.control-service");
|
|
5
7
|
const embedding_1 = require("../../embedding");
|
|
6
8
|
const scope_reference_1 = require("../../scope-reference");
|
|
@@ -78,12 +80,110 @@ class FAM_Retrieval_ControlService {
|
|
|
78
80
|
const tableHits = await this.searchTable({ table, query, queryVector, scopeExpand, config, includeContent });
|
|
79
81
|
allHits.push(...tableHits);
|
|
80
82
|
}
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
+
// ④b FEAT-003 — determinisztikus kód-expanzió: ha a query azonosító-kódot tartalmaz, az ÖSSZES
|
|
84
|
+
// citáló elem maximális score-ral (exact citation) a vektor-zaj fölé kerül. Always-on, strictly
|
|
85
|
+
// additive (kód-mentes query-n no-op); a merge id szerint dedup-ol (vektor+kód ugyanazon elemen → felülemel).
|
|
86
|
+
const detectedCodes = fam_reference_code_util_1.FAM_ReferenceCode_Util.extract(query.query);
|
|
87
|
+
const mergedHits = await this.applyCodeExpansion({ query, detectedCodes, scopeExpand, vectorHits: allHits, includeContent });
|
|
88
|
+
// FEAT-006 context-injection deduper: a hívó által MÁR injektált `_id`-k kizárása (a minScore/topK ELŐTT,
|
|
89
|
+
// hogy ne foglaljanak helyet) — egy session ne kapja vissza ugyanazt a kontextust kétszer. A kizárás a
|
|
90
|
+
// kód-exact-re IS vonatkozik (ha a session már birtokolja, nem kell újra, F3-1 ellenére sem).
|
|
91
|
+
const visibleHits = this.excludeSeen(mergedHits, query.excludeIds);
|
|
92
|
+
// ⑤ weight-szorzó MÁR a hit-eken (finalScore). ⑥ minScore-küszöb → relevantSet — DE F3-2: a kód-exact
|
|
93
|
+
// citáció (`matchedCodes` nem-üres) BYPASS-olja a minScore-t (a determinisztikusan citált kód eleme
|
|
94
|
+
// releváns a score-tól függetlenül; a `weight≤0` továbbra is kizárt, mert az már a merge ELŐTT kiesik).
|
|
95
|
+
const relevantSet = visibleHits.filter((hit) => this.codeExactRank(hit) > 0 || hit.finalScore >= config.minScore);
|
|
83
96
|
// ⑦ rendezés (finalScore desc → recency → _id asc).
|
|
84
97
|
this.sortHits(relevantSet, config.scoreOrder);
|
|
85
98
|
// ⑧ topK-vágás + dense-detektálás + suggestions.
|
|
86
|
-
return this.buildResult({ query, relevantSet, config, scopeExpand });
|
|
99
|
+
return this.buildResult({ query, relevantSet, config, scopeExpand, detectedCodes });
|
|
100
|
+
}
|
|
101
|
+
// =========================================================================
|
|
102
|
+
// ④b KÓD-EXPANZIÓ (FEAT-003) — determinisztikus link-retrieval a vektor MELLÉ
|
|
103
|
+
// =========================================================================
|
|
104
|
+
/**
|
|
105
|
+
* A determinisztikus kód-expanzió (FEAT-003). A `detectedCodes` (a query-ből `FAM_ReferenceCode_Util`-lal
|
|
106
|
+
* felismert kódok) MINDEN cél-tárban `find({ referenceCodes: $in })`-nel behúzza az ÖSSZES citáló elemet
|
|
107
|
+
* (az AZONOS scope/tag/kind/active prefilterrel), `embeddingStatus`-tól FÜGGETLENÜL. A vektor-találatokkal
|
|
108
|
+
* id szerint merge-el (dedup): ha egy elem vektor-hit IS, a kód-citáció felülemeli a score-ját (exact >
|
|
109
|
+
* hasonlóság) + jelöli `matchedCodes`-szal. Kód-mentes query → a vektor-találatok változatlanul.
|
|
110
|
+
*/
|
|
111
|
+
async applyCodeExpansion(set) {
|
|
112
|
+
if (!set.detectedCodes.length) {
|
|
113
|
+
return set.vectorHits;
|
|
114
|
+
}
|
|
115
|
+
// id→hit index a dedup-merge-hez (a vektor-találatok elsőként kerülnek be).
|
|
116
|
+
const byId = new Map();
|
|
117
|
+
for (const hit of set.vectorHits) {
|
|
118
|
+
byId.set(hit.id, hit);
|
|
119
|
+
}
|
|
120
|
+
for (const table of set.query.tables) {
|
|
121
|
+
const codeEntries = await new fam_retrieval_candidate_data_service_1.FAM_RetrievalCandidate_DataService().loadByReferenceCodes({
|
|
122
|
+
table: table,
|
|
123
|
+
scopeIdSet: set.scopeExpand.scopeIdSet,
|
|
124
|
+
tagFilter: set.query.tagFilter,
|
|
125
|
+
kindFilter: set.query.kindFilter,
|
|
126
|
+
}, set.detectedCodes);
|
|
127
|
+
for (const entry of codeEntries) {
|
|
128
|
+
this.mergeCodeHit({ byId, entry, table, detectedCodes: set.detectedCodes, includeContent: set.includeContent });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return Array.from(byId.values());
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Egy kód-citáló entry beolvasztása a hit-halmazba (FEAT-003). `score=1.0` (exact citation = maximális
|
|
135
|
+
* relevancia), `finalScore=1.0×weight` (a meglévő weight-modellbe illesztve). `weight≤0` → kizárt
|
|
136
|
+
* (konzisztens a vektor-ággal). Ha az elem MÁR vektor-hit → a meglévő hit-et frissítjük (`matchedCodes`
|
|
137
|
+
* + a magasabb score felülír), egyébként új code-hit-et szúrunk.
|
|
138
|
+
*/
|
|
139
|
+
mergeCodeHit(set) {
|
|
140
|
+
const id = set.entry._id;
|
|
141
|
+
if (!id) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const weight = set.entry.weight ?? 1;
|
|
145
|
+
if (weight <= 0) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const matched = (set.entry.referenceCodes ?? []).filter((code) => set.detectedCodes.includes(code));
|
|
149
|
+
if (!matched.length) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const codeFinalScore = 1.0 * weight;
|
|
153
|
+
const existing = set.byId.get(id);
|
|
154
|
+
if (existing) {
|
|
155
|
+
existing.matchedCodes = matched;
|
|
156
|
+
if (codeFinalScore > existing.finalScore) {
|
|
157
|
+
existing.score = 1.0;
|
|
158
|
+
existing.finalScore = codeFinalScore;
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
set.byId.set(id, this.toCodeHit({ entry: set.entry, id, table: set.table, weight, matched, includeContent: set.includeContent }));
|
|
163
|
+
}
|
|
164
|
+
/** Egy kód-citáló entry → `FAM_RetrievalHit` (exact citation: `score=1.0`, `matchedCodes` kitöltve). */
|
|
165
|
+
toCodeHit(set) {
|
|
166
|
+
const lastModified = set.entry.__lastModified;
|
|
167
|
+
return {
|
|
168
|
+
id: set.id,
|
|
169
|
+
table: set.table,
|
|
170
|
+
score: 1.0,
|
|
171
|
+
weight: set.weight,
|
|
172
|
+
finalScore: 1.0 * set.weight,
|
|
173
|
+
content: set.includeContent ? set.entry.content : undefined,
|
|
174
|
+
kind: set.entry.kind,
|
|
175
|
+
tags: set.entry.tags ?? [],
|
|
176
|
+
...this.ruleMeta(set.entry, set.table),
|
|
177
|
+
scopePath: (set.entry.scopePath ?? []),
|
|
178
|
+
source: set.entry.source,
|
|
179
|
+
sourceFilePath: set.entry.sourceFilePath ?? null,
|
|
180
|
+
absolutePath: set.entry.source?.absolutePath ?? null,
|
|
181
|
+
chunkType: set.entry.chunkType,
|
|
182
|
+
headingPath: set.entry.headingPath ?? [],
|
|
183
|
+
position: set.entry.position,
|
|
184
|
+
matchedCodes: set.matched,
|
|
185
|
+
lastModifiedMs: lastModified ? new Date(lastModified).getTime() : 0,
|
|
186
|
+
};
|
|
87
187
|
}
|
|
88
188
|
// =========================================================================
|
|
89
189
|
// ②–④ PER-TÁR: prefilter → vektor-keresés → hit-row (SP-5.1/SP-5.2)
|
|
@@ -96,33 +196,65 @@ class FAM_Retrieval_ControlService {
|
|
|
96
196
|
* dense-detektálás a topK ELŐTTI halmazon számol; dsgn-005 §4.1 / SP-5.2 feladat).
|
|
97
197
|
*/
|
|
98
198
|
async searchTable(set) {
|
|
99
|
-
// ①–②
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
199
|
+
// ①–② A jelölt-betöltés + ④ vektor-keresés — két út AZONOS EREDMÉNNYEL (a `byId` index a hit-row +
|
|
200
|
+
// weight + recency összefűzéséhez):
|
|
201
|
+
// • UNSCOPED (nincs scope/tag/kind szűrő, nem `rules`): a prefilter a TELJES tárat adná, ezért fölösleges
|
|
202
|
+
// mind a ~23k jelöltet betölteni — a candidateId-k = a teljes pool. Helyette közvetlenül a (hidratált)
|
|
203
|
+
// main-poolon keresünk (top-`MAX_PAGE_SIZE`), és CSAK a hit-ek metaadatát töltjük (`loadByIds`) → a
|
|
204
|
+
// Mongo-munka ~242×-ére csökken (mért: 727ms → 3ms). A pool = az összes aktív, completed jelölt, a
|
|
205
|
+
// weight a hit-en alkalmazva → BIT-AZONOS rangsor a régi (teljes-prefilter) úttal (`min(23k,500)=500`).
|
|
206
|
+
// • SCOPED (scope/tag/kind/`rules`): a Mongo-prefilter szűkít → candidateId-halmaz → szűkített keresés.
|
|
207
|
+
const isUnscoped = !set.scopeExpand.scopeIdSet
|
|
208
|
+
&& !(set.query.tagFilter && set.query.tagFilter.length)
|
|
209
|
+
&& !set.query.kindFilter
|
|
210
|
+
&& set.table !== fam_table_type_enum_1.FAM_Table.rules;
|
|
110
211
|
const byId = new Map();
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
212
|
+
let vectorHits;
|
|
213
|
+
if (isUnscoped) {
|
|
214
|
+
// ④ vektor-keresés a TELJES poolon (a vektor-réteg `limit`-je = `MAX_PAGE_SIZE` relevantSet a
|
|
215
|
+
// dense-detektáláshoz — NEM a végső topK).
|
|
216
|
+
vectorHits = await embedding_1.FAM_VectorSearch_ControlService.getInstance().search({
|
|
217
|
+
table: set.table,
|
|
218
|
+
queryVector: set.queryVector,
|
|
219
|
+
topK: MAX_PAGE_SIZE,
|
|
220
|
+
});
|
|
221
|
+
if (!vectorHits.length) {
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
const hitEntries = await new fam_retrieval_candidate_data_service_1.FAM_RetrievalCandidate_DataService()
|
|
225
|
+
.loadByIds(set.table, vectorHits.map((hit) => hit.id));
|
|
226
|
+
for (const entry of hitEntries) {
|
|
227
|
+
if (entry._id) {
|
|
228
|
+
byId.set(entry._id, entry);
|
|
229
|
+
}
|
|
116
230
|
}
|
|
117
231
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
232
|
+
else {
|
|
233
|
+
// ①–② Mongo-prefilter → jelölt-entry-k (a soft-delete + rules.isActive guard beépített).
|
|
234
|
+
const candidates = await new fam_retrieval_candidate_data_service_1.FAM_RetrievalCandidate_DataService().loadCandidates({
|
|
235
|
+
table: set.table,
|
|
236
|
+
scopeIdSet: set.scopeExpand.scopeIdSet,
|
|
237
|
+
tagFilter: set.query.tagFilter,
|
|
238
|
+
kindFilter: set.query.kindFilter,
|
|
239
|
+
});
|
|
240
|
+
if (!candidates.length) {
|
|
241
|
+
return [];
|
|
242
|
+
}
|
|
243
|
+
const candidateIds = [];
|
|
244
|
+
for (const entry of candidates) {
|
|
245
|
+
if (entry._id) {
|
|
246
|
+
byId.set(entry._id, entry);
|
|
247
|
+
candidateIds.push(entry._id);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// ④ vektor-keresés a szűkített poolon (a relevantSet a dense-detektáláshoz, NEM a végső topK).
|
|
251
|
+
vectorHits = await embedding_1.FAM_VectorSearch_ControlService.getInstance().search({
|
|
252
|
+
table: set.table,
|
|
253
|
+
queryVector: set.queryVector,
|
|
254
|
+
candidateIds: candidateIds,
|
|
255
|
+
topK: Math.min(candidateIds.length, MAX_PAGE_SIZE),
|
|
256
|
+
});
|
|
257
|
+
}
|
|
126
258
|
// ⑤ weight-szorzó: finalScore = score × (weight ?? 1); weight ≤ 0 → kizárt (nem kerül a hits közé).
|
|
127
259
|
const hits = [];
|
|
128
260
|
for (const vectorHit of vectorHits) {
|
|
@@ -134,28 +266,66 @@ class FAM_Retrieval_ControlService {
|
|
|
134
266
|
if (weight <= 0) {
|
|
135
267
|
continue;
|
|
136
268
|
}
|
|
137
|
-
hits.push(this.toHit({
|
|
269
|
+
hits.push(this.toHit({
|
|
270
|
+
entry, vectorHit, table: set.table, weight,
|
|
271
|
+
importChunkWeight: set.config.importChunkWeight, includeContent: set.includeContent,
|
|
272
|
+
}));
|
|
138
273
|
}
|
|
139
274
|
return hits;
|
|
140
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* Az import-only chunkok (`chunkType:"imports"`) read-idejű rangsor-szorzója (FAM-REV-060). Egy import-sor
|
|
278
|
+
* (`import { X } from "./y"`) magas cosine-t kap egy szimbólum-név query-re, de alacsony az ÉRTÉKe (csak
|
|
279
|
+
* import, nem definíció) → `importChunkWeight` (<1) a definíció-chunkok ALÁ rangsorolja. Minden más chunkType
|
|
280
|
+
* (class/function/interface/generic/…) érintetlen (×1). READ-time, a nyers `score` (relevancia) változatlan.
|
|
281
|
+
*/
|
|
282
|
+
chunkTypeFactor(chunkType, importChunkWeight) {
|
|
283
|
+
return chunkType === 'imports' ? importChunkWeight : 1;
|
|
284
|
+
}
|
|
141
285
|
/** Egy candidate-entry + a vektor-hit → `FAM_RetrievalHit` (finalScore + denormalizált mezők). */
|
|
142
286
|
toHit(set) {
|
|
143
287
|
const lastModified = set.entry.__lastModified;
|
|
288
|
+
// ⑤ weight-szorzó + FAM-REV-060 import-chunk read-faktor: finalScore = score × weight × chunkTypeFactor.
|
|
289
|
+
const chunkFactor = this.chunkTypeFactor(set.entry.chunkType, set.importChunkWeight);
|
|
144
290
|
return {
|
|
145
291
|
id: set.vectorHit.id,
|
|
146
292
|
table: set.table,
|
|
147
293
|
score: set.vectorHit.score,
|
|
148
294
|
weight: set.weight,
|
|
149
|
-
finalScore: set.vectorHit.score * set.weight,
|
|
295
|
+
finalScore: set.vectorHit.score * set.weight * chunkFactor,
|
|
150
296
|
content: set.includeContent ? set.entry.content : undefined,
|
|
151
297
|
kind: set.entry.kind,
|
|
152
298
|
tags: set.entry.tags ?? [],
|
|
299
|
+
...this.ruleMeta(set.entry, set.table),
|
|
153
300
|
scopePath: (set.entry.scopePath ?? []),
|
|
154
301
|
source: set.entry.source,
|
|
155
302
|
sourceFilePath: set.entry.sourceFilePath ?? null,
|
|
303
|
+
absolutePath: set.entry.source?.absolutePath ?? null,
|
|
304
|
+
// Location-infó (FAM-REV-049): chunk-típus + symbol-lánc + sor/char-pozíció — hogy keresésből
|
|
305
|
+
// rögtön „melyik fájl / melyik symbol / hányadik sor" legyen, ne csak a fájlnév.
|
|
306
|
+
chunkType: set.entry.chunkType,
|
|
307
|
+
headingPath: set.entry.headingPath ?? [],
|
|
308
|
+
position: set.entry.position,
|
|
156
309
|
lastModifiedMs: lastModified ? new Date(lastModified).getTime() : 0,
|
|
157
310
|
};
|
|
158
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* A `rules` tár per-tár metaadatai a hit-en (`category`/`isActive`/`applicableScopes`) — FEAT-005/006 P1:
|
|
314
|
+
* a zóna-alapú aktív-rule kezelés alapja, hogy a hívó/hook a keresési hit-ből is lássa, MI aktív + mely
|
|
315
|
+
* zónában. MÁS táraknál üres objektum (e mezők a `FAM_Entry` bázison nincsenek; a `rules`-doc-on igen —
|
|
316
|
+
* a loose `Partial<FAM_Rules_DataModel>` view a meglévő `as`-konvenció szerint, mint a `source`/`scopePath`).
|
|
317
|
+
*/
|
|
318
|
+
ruleMeta(entry, table) {
|
|
319
|
+
if (table !== fam_table_type_enum_1.FAM_Table.rules) {
|
|
320
|
+
return {};
|
|
321
|
+
}
|
|
322
|
+
const rule = entry;
|
|
323
|
+
return {
|
|
324
|
+
category: rule.category,
|
|
325
|
+
isActive: rule.isActive,
|
|
326
|
+
applicableScopes: (rule.applicableScopes ?? []),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
159
329
|
// =========================================================================
|
|
160
330
|
// ⑦ RENDEZÉS (SP-5.2, dsgn-005 §5)
|
|
161
331
|
// =========================================================================
|
|
@@ -167,6 +337,14 @@ class FAM_Retrieval_ControlService {
|
|
|
167
337
|
*/
|
|
168
338
|
sortHits(hits, scoreOrder) {
|
|
169
339
|
hits.sort((a, b) => {
|
|
340
|
+
// F3-1 (garantált kód-exact inclusion): a determinisztikus kód-citáció (`matchedCodes` nem-üres) a
|
|
341
|
+
// legkülső rendezési kulcs — a fuzzy vektor-hasonlóság ELÉ rangsorol (decisiveness). Az
|
|
342
|
+
// `effectiveTopK = max(topK, #codeExact)`-kal együtt ez GARANTÁLJA, hogy a citált kód MINDEN eleme
|
|
343
|
+
// visszakerül (a topK sosem vágja le). Kód-mentes query-n nincs code-exact hit → változatlan rangsor.
|
|
344
|
+
const codePriority = this.codeExactRank(b) - this.codeExactRank(a);
|
|
345
|
+
if (codePriority !== 0) {
|
|
346
|
+
return codePriority;
|
|
347
|
+
}
|
|
170
348
|
const primary = this.primaryComparator(a, b, scoreOrder);
|
|
171
349
|
if (primary !== 0) {
|
|
172
350
|
return primary;
|
|
@@ -190,36 +368,58 @@ class FAM_Retrieval_ControlService {
|
|
|
190
368
|
// 'weighted' (default).
|
|
191
369
|
return b.finalScore - a.finalScore;
|
|
192
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* F3-1: egy hit **kód-exact**-e (van legalább egy `matchedCodes` determinisztikus citáció)? Rendezési kulcs
|
|
373
|
+
* (1 = kód-exact → előre, 0 = pusztán vektor-hit) és a garantált-inclusion `effectiveTopK` számlálója.
|
|
374
|
+
*/
|
|
375
|
+
codeExactRank(hit) {
|
|
376
|
+
return hit.matchedCodes && hit.matchedCodes.length ? 1 : 0;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* FEAT-006 context-injection deduper: a hívó által MÁR injektált (`excludeIds`) bejegyzések kizárása a
|
|
380
|
+
* találatokból. Üres/hiányzó lista → változatlan halmaz (nincs fölös allokáció). Állapotmentes: a
|
|
381
|
+
* „már látott" halmazt a hívó (session/hook) tartja.
|
|
382
|
+
*/
|
|
383
|
+
excludeSeen(hits, excludeIds) {
|
|
384
|
+
if (!excludeIds || !excludeIds.length) {
|
|
385
|
+
return hits;
|
|
386
|
+
}
|
|
387
|
+
const seen = new Set(excludeIds);
|
|
388
|
+
return hits.filter((hit) => !seen.has(hit.id));
|
|
389
|
+
}
|
|
193
390
|
// =========================================================================
|
|
194
391
|
// ⑧ topK-vágás + DENSE-DETEKTÁLÁS + signaling (SP-5.3, dsgn-005 §4)
|
|
195
392
|
// =========================================================================
|
|
196
393
|
/**
|
|
197
|
-
* A rendezett relevantSet → `FAM_RetrievalResult` (dsgn-005 §4)
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
394
|
+
* A rendezett relevantSet → `FAM_RetrievalResult` (dsgn-005 §4) — **confidence-padlóval** (FAM-REV-054,
|
|
395
|
+
* 2026-06-20 dogfood). A relevancia-JELZÉSEK (`totalRelevant`/`denseResults`/`truncated`/`suggestions`)
|
|
396
|
+
* a MAGABIZTOSAN releváns halmazból (`score >= relevanceFloor`, nyers cosine-on, hogy a weight ne torzítson)
|
|
397
|
+
* számolnak — NEM a `minScore=0` melletti teljes poolból (ami minden cosine>0 elemet „relevánsnak" vett,
|
|
398
|
+
* így minden query-n hamis „dense + N=pool" + „emeld a topK-t a pool-ra" jött). A `returnedHits` viszont
|
|
399
|
+
* best-effort marad (recall): a hívó megkapja a topK legjobbat, DE ha egyik sem magabiztos, a jelek
|
|
400
|
+
* ŐSZINTÉN mutatják (north-star: „ha valami hiányzik, MEGMONDJA" — `confidentSet` üres → low-confidence note).
|
|
401
|
+
* Dense dual-gate (§4.2): `(N >= ratio×topK) || (N >= absMin)`, csak `truncated:true` mellett. Az
|
|
402
|
+
* `uncertaintyNotes` a scope-prefilter jegyzetei (dsgn-002 §4). **Spec-drift:** dsgn-005 §4.1 jelenleg
|
|
403
|
+
* `totalRelevant = relevantSet.length`-et ír; ez a confidence-floor-os finomítás (a katalógus + REVIEW-FINDINGS jelöli).
|
|
202
404
|
*/
|
|
203
405
|
buildResult(set) {
|
|
204
|
-
|
|
205
|
-
|
|
406
|
+
// F3-1 (garantált kód-exact inclusion): az effektív topK a kért `topK` ÉS a kód-exact citációk
|
|
407
|
+
// száma közül a nagyobb — így a determinisztikusan citált kód MINDEN eleme visszakerül, akkor is, ha
|
|
408
|
+
// többen vannak, mint a kért `topK` (a `sortHits` ezeket a halmaz elejére rendezte). Kód-mentes
|
|
409
|
+
// query-n `#codeExact = 0` → `effectiveTopK = topK` (változatlan viselkedés).
|
|
410
|
+
const codeExactCount = set.relevantSet.filter((hit) => this.codeExactRank(hit)).length;
|
|
411
|
+
const effectiveTopK = Math.max(set.config.topK, codeExactCount);
|
|
206
412
|
const returnedHits = set.relevantSet.slice(0, effectiveTopK);
|
|
413
|
+
// Magabiztosan releváns halmaz: a nyers cosine `score` (NEM finalScore — a weight ranking-boost, nem
|
|
414
|
+
// relevancia) a `relevanceFloor` felett. A relevantSet rendezett, ezért a confidentSet is.
|
|
415
|
+
const confidentSet = set.relevantSet.filter((hit) => hit.score >= set.config.relevanceFloor);
|
|
416
|
+
const totalRelevant = confidentSet.length;
|
|
207
417
|
const truncated = totalRelevant > effectiveTopK;
|
|
208
|
-
// Dual-gate dense (§4.2) — csak `truncated:true` mellett
|
|
418
|
+
// Dual-gate dense (§4.2) — a CONFIDENT halmazon, csak `truncated:true` mellett.
|
|
209
419
|
const densenessRatio = totalRelevant / effectiveTopK;
|
|
210
420
|
const denseByRatio = densenessRatio >= set.config.denseResultRatio;
|
|
211
421
|
const denseByAbs = totalRelevant >= set.config.denseResultAbsMin;
|
|
212
422
|
const denseResults = truncated && (denseByRatio || denseByAbs);
|
|
213
|
-
// Suggestions (§4.4) — a relevantSet valós statisztikáiból + a query paramétereiből.
|
|
214
|
-
const suggestions = (denseResults || truncated)
|
|
215
|
-
? fam_retrieval_suggestions_util_1.FAM_RetrievalSuggestions_Util.generate({
|
|
216
|
-
relevantSet: set.relevantSet,
|
|
217
|
-
query: set.query,
|
|
218
|
-
effectiveTopK: effectiveTopK,
|
|
219
|
-
truncated: truncated,
|
|
220
|
-
maxPageSize: MAX_PAGE_SIZE,
|
|
221
|
-
})
|
|
222
|
-
: [];
|
|
223
423
|
return {
|
|
224
424
|
query: set.query.query,
|
|
225
425
|
tables: set.query.tables,
|
|
@@ -227,10 +427,45 @@ class FAM_Retrieval_ControlService {
|
|
|
227
427
|
denseResults: denseResults,
|
|
228
428
|
totalRelevant: totalRelevant,
|
|
229
429
|
truncated: truncated,
|
|
230
|
-
suggestions:
|
|
430
|
+
suggestions: this.buildSuggestions({
|
|
431
|
+
relevantSet: set.relevantSet,
|
|
432
|
+
confidentSet: confidentSet,
|
|
433
|
+
query: set.query,
|
|
434
|
+
effectiveTopK: effectiveTopK,
|
|
435
|
+
truncated: truncated,
|
|
436
|
+
relevanceFloor: set.config.relevanceFloor,
|
|
437
|
+
}),
|
|
231
438
|
uncertaintyNotes: set.scopeExpand.uncertaintyNotes,
|
|
439
|
+
detectedCodes: set.detectedCodes,
|
|
232
440
|
};
|
|
233
441
|
}
|
|
442
|
+
/**
|
|
443
|
+
* A suggestions-lista (FAM-REV-054). HA nincs magabiztos találat, de adtunk vissza best-effort hit-et →
|
|
444
|
+
* ŐSZINTE low-confidence jelzés (north-star: „megmondja, ha valószínűleg nincs lefedve") a nyers top-score-ral.
|
|
445
|
+
* Egyébként a szűkítési javaslatok a CONFIDENT halmaz statisztikáiból (nem a teljes zaj-poolból), és csak
|
|
446
|
+
* ha `truncated` (van a topK-n túli magabiztos elem). Kód-citáció (`score=1.0`) mindig magabiztos.
|
|
447
|
+
*/
|
|
448
|
+
buildSuggestions(set) {
|
|
449
|
+
if (!set.confidentSet.length) {
|
|
450
|
+
if (!set.relevantSet.length) {
|
|
451
|
+
return [];
|
|
452
|
+
}
|
|
453
|
+
const topScore = set.relevantSet[0].score;
|
|
454
|
+
return [`⚠️ nincs magabiztosan releváns találat (a legjobb score ${topScore.toFixed(2)} < `
|
|
455
|
+
+ `relevanceFloor ${set.relevanceFloor.toFixed(2)}) — lehet, hogy ez nincs lefedve; `
|
|
456
|
+
+ `fogalmazd át a query-t, vagy tágítsd/váltsd a scope-ot/tárat`];
|
|
457
|
+
}
|
|
458
|
+
if (!set.truncated) {
|
|
459
|
+
return [];
|
|
460
|
+
}
|
|
461
|
+
return fam_retrieval_suggestions_util_1.FAM_RetrievalSuggestions_Util.generate({
|
|
462
|
+
relevantSet: set.confidentSet,
|
|
463
|
+
query: set.query,
|
|
464
|
+
effectiveTopK: set.effectiveTopK,
|
|
465
|
+
truncated: set.truncated,
|
|
466
|
+
maxPageSize: MAX_PAGE_SIZE,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
234
469
|
// =========================================================================
|
|
235
470
|
// CONFIG-FELOLDÁS (SP-5.2, dsgn-005 §6 / dsgn-007 SSOT) + query-embedding (SP-5.2 ④)
|
|
236
471
|
// =========================================================================
|
|
@@ -244,9 +479,15 @@ class FAM_Retrieval_ControlService {
|
|
|
244
479
|
async resolveReadConfig(table, query) {
|
|
245
480
|
const config = config_control_service_1.FAM_Config_ControlService.getInstance();
|
|
246
481
|
const ctx = { table: table };
|
|
247
|
-
// query.topK / query.minScore felülírja a config-default-ot (dsgn-003 §2.1 query-opció).
|
|
248
|
-
|
|
482
|
+
// query.topK / query.minScore felülírja a config-default-ot (dsgn-003 §2.1 query-opció). A `depth:'deep'`
|
|
483
|
+
// (mély feltárás) az alap-topK-t a `read.deepTopK`-ra emeli (sokkal több chunk) — az explicit `topK` nyer.
|
|
484
|
+
const isDeep = query.depth === 'deep';
|
|
485
|
+
const topKDefault = query.topK ?? (isDeep
|
|
486
|
+
? await this.resolveNumber(config, 'read.deepTopK', ctx, 25)
|
|
487
|
+
: await this.resolveNumber(config, 'read.topK', ctx, 5));
|
|
249
488
|
const minScore = query.minScore ?? await this.resolveNumber(config, 'read.minScore', ctx, 0);
|
|
489
|
+
const relevanceFloor = await this.resolveNumber(config, 'read.relevanceFloor', ctx, 0.5);
|
|
490
|
+
const importChunkWeight = await this.resolveNumber(config, 'read.importChunkWeight', ctx, 0.4);
|
|
250
491
|
const denseResultRatio = await this.resolveNumber(config, 'read.denseResultRatio', ctx, 2.0);
|
|
251
492
|
const denseResultAbsMin = await this.resolveNumber(config, 'read.denseResultAbsMin', ctx, 25);
|
|
252
493
|
const scoreOrderValue = (await config.resolve('read.scoreOrder', ctx)).value;
|
|
@@ -254,6 +495,8 @@ class FAM_Retrieval_ControlService {
|
|
|
254
495
|
return {
|
|
255
496
|
topK: Math.min(topKDefault, MAX_PAGE_SIZE),
|
|
256
497
|
minScore: minScore,
|
|
498
|
+
relevanceFloor: relevanceFloor,
|
|
499
|
+
importChunkWeight: importChunkWeight,
|
|
257
500
|
denseResultRatio: denseResultRatio,
|
|
258
501
|
denseResultAbsMin: denseResultAbsMin,
|
|
259
502
|
scoreOrder: scoreOrder,
|
|
@@ -20,8 +20,11 @@ class FAM_ScopeNormalize_Util {
|
|
|
20
20
|
/**
|
|
21
21
|
* Egy nyers token normalizálása a de-dup / exact-match kulcshoz (dsgn-002 §3.1). A lépések:
|
|
22
22
|
* (1) lowercase (Unicode-aware), (2) ékezet-fold (NFD + diakritika-strip — `"Adventör"` →
|
|
23
|
-
* `"adventor"`), (3)
|
|
24
|
-
*
|
|
23
|
+
* `"adventor"`), (3) **szeparátor-egységesítés** (FAM-REV-042): minden whitespace / `-` / `_`
|
|
24
|
+
* futam egyetlen szóközre — így `"auth-service"` ≡ `"auth service"` ≡ `"auth_service"` (a
|
|
25
|
+
* kötőjel-vs-alulvonás-vs-szóköz variánsok NEM hoznak létre dupla scope-ot), (4) trim. Üres/nem-string
|
|
26
|
+
* bemenet → üres string (a hívó dönti el, hogy az üres kulcs match-elhet-e). A tárolt `canonicalName`
|
|
27
|
+
* az EREDETI marad — a match futásidőben mindkét oldalt normalizálja, így nincs adatmigráció.
|
|
25
28
|
*/
|
|
26
29
|
static normalize(raw) {
|
|
27
30
|
if (!raw) {
|
|
@@ -31,7 +34,7 @@ class FAM_ScopeNormalize_Util {
|
|
|
31
34
|
.toLowerCase()
|
|
32
35
|
.normalize('NFD')
|
|
33
36
|
.replace(FAM_ScopeNormalize_Util.DIACRITICS, '')
|
|
34
|
-
.replace(
|
|
37
|
+
.replace(/[\s_-]+/g, ' ')
|
|
35
38
|
.trim();
|
|
36
39
|
}
|
|
37
40
|
/**
|
|
@@ -62,6 +62,10 @@ class FAM_Reference_DataService extends nts_dynamo_1.DyNTS_DataService {
|
|
|
62
62
|
return null;
|
|
63
63
|
}
|
|
64
64
|
for (const reference of references) {
|
|
65
|
+
// Negatív-alias veto (disambiguáció): ez a reference EXPLICIT nem oldja fel a tokent → kihagyjuk.
|
|
66
|
+
if (FAM_Reference_DataService.vetoesToken(targetKey, reference)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
65
69
|
if (fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(reference.canonicalTerm) === targetKey) {
|
|
66
70
|
return reference;
|
|
67
71
|
}
|
|
@@ -74,6 +78,20 @@ class FAM_Reference_DataService extends nts_dynamo_1.DyNTS_DataService {
|
|
|
74
78
|
}
|
|
75
79
|
return null;
|
|
76
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Veto-zza-e a reference a tokent (negatív-alias, „ne keverjünk dolgokat"): a token (normalizáltan) egyezik-e
|
|
83
|
+
* a reference bármely `negativeAliases`-ével. A tokent MAGA normalizálja (a `FAM_ScopeNormalize_Util` choke-
|
|
84
|
+
* ponton — idempotens, ha a hívó már normalizált adott át) → szeparátor-/ékezet-/kis-nagybetű-robusztus. Ha
|
|
85
|
+
* igen, ez a reference NEM kandidátus a tokenre (exact + fuzzy + vektor egyaránt). Üres negatív-lista → false.
|
|
86
|
+
*/
|
|
87
|
+
static vetoesToken(token, reference) {
|
|
88
|
+
const target = fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(token);
|
|
89
|
+
if (!target.length) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return (reference.negativeAliases ?? [])
|
|
93
|
+
.some((negative) => fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(negative) === target);
|
|
94
|
+
}
|
|
77
95
|
// =========================================================================
|
|
78
96
|
// STRING-FUZZY (dsgn-002 §3.1, SP-3.2)
|
|
79
97
|
// =========================================================================
|