@futdevpro/fdp-agent-memory 1.1.21 → 1.1.112
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/package.json +2 -2
- 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 +152 -33
- 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/_collections/title.const.js +26 -26
- 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/capture/_services/fam-auto-capture.control-service.js +1 -1
- 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 +32 -6
- package/build/src/_modules/embedding/_services/fam-embedding.control-service.js +142 -9
- package/build/src/_modules/embedding/_services/fam-entry.data-service.js +32 -1
- 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 +177 -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/_collections/fam-core-tools.const.js +1 -1
- package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +264 -41
- package/build/src/_modules/mcp/_services/fam-read-tool.service.js +71 -1
- package/build/src/_modules/mcp/_services/fam-write-tool.service.js +36 -0
- package/build/src/_modules/migration/_services/fam-claude-mem-import.control-service.js +1 -1
- 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 +134 -0
- package/build/src/_modules/scope-reference/_services/fam-scope-maintenance.control-service.js +423 -0
- package/build/src/_modules/scope-reference/_services/fam-scope-resolver.control-service.js +2 -2
- package/build/src/_modules/scope-reference/_services/fam-scope.data-service.js +25 -0
- package/build/src/_modules/scope-reference/index.js +4 -1
- package/build/src/_routes/server/api/api.controller.js +324 -23
- package/build/src/_routes/server/api/fam-doctor.control-service.js +156 -0
- package/build/src/_routes/server/config/config.control-service.js +2 -2
- package/build/src/app.server.js +29 -27
- 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 +2 -2
- package/build/src/_cli/_collections/fam-project-discovery.util.js +0 -98
- package/build/src/_collections/fam-heap-guard.control-service.js +0 -176
- package/client-dist/chunk-I77GXVAQ.js +0 -1
- package/client-dist/main-PJPEDVJT.js +0 -2
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_ScopeMaintenance_Util = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* `FAM_ScopeMaintenance_Util` (user-FR 2026-06-22) — a **scope rename/merge** Mongo-update-specjeinek
|
|
6
|
+
* TISZTA (pure) felépítője. A scope-azonosító a denormalizált `scopePath[]`-en (minden RAG-entry) ÉS a
|
|
7
|
+
* reference `canonicalScopeRef`-jén (egyetlen objektum) szerepel; a rename/merge ezeket írja át bulk-ban.
|
|
8
|
+
*
|
|
9
|
+
* **Rename** = a `scopeId`-hez tartozó `canonicalName` átírása (a scopeId marad). **Merge** = a `from`
|
|
10
|
+
* scopeId minden előfordulása az `into` scope `{scopeId, layer, canonicalName}`-jére cserélve (majd a
|
|
11
|
+
* hívó soft-delete-eli a `from` scope-ot). A spec-építés determinisztikus + külön tesztelhető; az
|
|
12
|
+
* alkalmazás (mongoose `updateMany`) a control-service-ben.
|
|
13
|
+
*/
|
|
14
|
+
class FAM_ScopeMaintenance_Util {
|
|
15
|
+
/** RENAME — entry-collection (`scopePath[]`): a `scopeId`-elem `canonicalName`-jét `newName`-re. */
|
|
16
|
+
static renameEntryUpdate(scopeId, newName) {
|
|
17
|
+
return {
|
|
18
|
+
filter: { 'scopePath.scopeId': scopeId },
|
|
19
|
+
update: { $set: { 'scopePath.$[el].canonicalName': newName } },
|
|
20
|
+
arrayFilters: [{ 'el.scopeId': scopeId }],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** MERGE — entry-collection (`scopePath[]`): a `fromId`-elemet az `into` ref `{scopeId,layer,canonicalName}`-jére. */
|
|
24
|
+
static mergeEntryUpdate(fromId, into) {
|
|
25
|
+
return {
|
|
26
|
+
filter: { 'scopePath.scopeId': fromId },
|
|
27
|
+
update: {
|
|
28
|
+
$set: {
|
|
29
|
+
'scopePath.$[el].scopeId': into.scopeId,
|
|
30
|
+
'scopePath.$[el].layer': into.layer,
|
|
31
|
+
'scopePath.$[el].canonicalName': into.canonicalName,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
arrayFilters: [{ 'el.scopeId': fromId }],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** RENAME — reference (`canonicalScopeRef`, egyetlen objektum): a `canonicalName` átírása. */
|
|
38
|
+
static renameReferenceUpdate(scopeId, newName) {
|
|
39
|
+
return {
|
|
40
|
+
filter: { 'canonicalScopeRef.scopeId': scopeId },
|
|
41
|
+
update: { $set: { 'canonicalScopeRef.canonicalName': newName } },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** MERGE — reference (`canonicalScopeRef`): a `from` ref cseréje az `into`-ra. */
|
|
45
|
+
static mergeReferenceUpdate(fromId, into) {
|
|
46
|
+
return {
|
|
47
|
+
filter: { 'canonicalScopeRef.scopeId': fromId },
|
|
48
|
+
update: {
|
|
49
|
+
$set: {
|
|
50
|
+
'canonicalScopeRef.scopeId': into.scopeId,
|
|
51
|
+
'canonicalScopeRef.layer': into.layer,
|
|
52
|
+
'canonicalScopeRef.canonicalName': into.canonicalName,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* RESET — egy scope (scan-eredetű) entry-jeinek TÖRLÉS-szűrője (PURE, user-FR 2026-06-24 — recovery
|
|
59
|
+
* egy félre-routolt scan után). A `source.type` default `'scan'` → a manuális/agent/import-SZERZETT
|
|
60
|
+
* (pótolhatatlan) tartalmat SOHA nem törli (csak az újra-szkennelhető scan-chunkokat). Opcionális
|
|
61
|
+
* `sourceRootContains` SUBSTRING-szűrés a `source.root`-on (case-insensitive, literál-escape-elve) — a
|
|
62
|
+
* cross-root szennyezés SEBÉSZI eltávolításához: pl. a `ccap-revisioned` gyökerű entry-k a `ccap-client`
|
|
63
|
+
* scope-ból, a `ccap` saját entry-jeinek MEGTARTÁSÁVAL. `sourceRootContains` nélkül a scope MINDEN
|
|
64
|
+
* scan-entry-je (a teljes reset).
|
|
65
|
+
*/
|
|
66
|
+
static buildResetFilter(set) {
|
|
67
|
+
const filter = { 'scopePath.scopeId': set.scopeId };
|
|
68
|
+
const sourceType = (set.sourceType ?? 'scan').trim();
|
|
69
|
+
if (sourceType) {
|
|
70
|
+
filter['source.type'] = sourceType;
|
|
71
|
+
}
|
|
72
|
+
const needle = (set.sourceRootContains ?? '').trim();
|
|
73
|
+
if (needle) {
|
|
74
|
+
filter['source.root'] = { $regex: FAM_ScopeMaintenance_Util.escapeRegex(needle), $options: 'i' };
|
|
75
|
+
}
|
|
76
|
+
return filter;
|
|
77
|
+
}
|
|
78
|
+
/** Regex-metakarakter escape (PURE): a substring-szűrőt LITERÁLKÉNT illeszti (nincs véletlen regex-tágítás). */
|
|
79
|
+
static escapeRegex(value) {
|
|
80
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* `buildMergeSuggestions(groups)` (scope-prevenció, user-FR 2026-06-22) — TISZTA logika: a detektált
|
|
84
|
+
* duplikátum-csoportokból konkrét, futtatható `merge_scopes from→into` direktívák. A csoportonkénti `into`
|
|
85
|
+
* a **kanonikus** (megtartandó) scope: (1) preferáltan a parented (= tipikusan scan-eredetű, org-fán lóg),
|
|
86
|
+
* (2) azon belül/különben a LEGTÖBB entry-jű (a legtöbb tudást hordozó). A többi tag → `from` (átpontozandó).
|
|
87
|
+
*
|
|
88
|
+
* **Prioritás:** a `'repo-key'` csoportok ELŐBB (ez a legbiztosabb identitás — a KÜLÖNBÖZŐ nevű, AZONOS repo
|
|
89
|
+
* eseteket is elkapja), majd a `'name'` csoportok. Egy `from` scope CSAK EGYSZER javasolt (a már elköltött
|
|
90
|
+
* scope-okat — akár from, akár egy korábbi into — kihagyjuk → nincs ütköző/körkörös merge). Determinisztikus
|
|
91
|
+
* (entryCount-, majd scopeId-rendezés). NEM mutál — csak javaslat; az alkalmazás a `mergeScopes` (dryRun).
|
|
92
|
+
*/
|
|
93
|
+
static buildMergeSuggestions(groups) {
|
|
94
|
+
const suggestions = [];
|
|
95
|
+
const consumed = new Set();
|
|
96
|
+
// A 'repo-key' csoportok előbb (biztosabb identitás), majd a 'name'.
|
|
97
|
+
const ordered = groups.slice().sort((a, b) => (a.groupKind === b.groupKind ? 0 : (a.groupKind === 'repo-key' ? -1 : 1)));
|
|
98
|
+
for (const group of ordered) {
|
|
99
|
+
const available = group.members.filter((member) => !consumed.has(member.scopeId));
|
|
100
|
+
if (available.length < 2) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const into = FAM_ScopeMaintenance_Util.chooseMergeInto(available);
|
|
104
|
+
consumed.add(into.scopeId);
|
|
105
|
+
for (const member of available) {
|
|
106
|
+
if (member.scopeId === into.scopeId || consumed.has(member.scopeId)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
consumed.add(member.scopeId);
|
|
110
|
+
suggestions.push({
|
|
111
|
+
from: { scopeId: member.scopeId, canonicalName: member.canonicalName },
|
|
112
|
+
into: { scopeId: into.scopeId, canonicalName: into.canonicalName },
|
|
113
|
+
reason: group.groupKind === 'repo-key'
|
|
114
|
+
? `Azonos kanonikus repo-kulcs ('${group.key}') — különböző nevű scope-ok ugyanarra a repóra mutatnak.`
|
|
115
|
+
: `Azonos (layer|név) duplikátum ('${group.key}') — a parented/nagyobb scope a megtartandó.`,
|
|
116
|
+
fromEntryCount: member.entryCount,
|
|
117
|
+
intoEntryCount: into.entryCount,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return suggestions;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* A megtartandó (`into`) scope kiválasztása egy duplikátum-csoportból (PURE): preferáltan a parented (van
|
|
125
|
+
* `parentScopeId` — az org-fán lógó, tipikusan scan-eredetű), azon belül/különben a LEGTÖBB entry-jű; döntetlen
|
|
126
|
+
* → a kisebb `scopeId` (determinizmus). A csoportnak legalább 1 tagja kell legyen.
|
|
127
|
+
*/
|
|
128
|
+
static chooseMergeInto(members) {
|
|
129
|
+
const parented = members.filter((member) => !!member.parentScopeId);
|
|
130
|
+
const pool = parented.length ? parented : members;
|
|
131
|
+
return pool.slice().sort((a, b) => (b.entryCount - a.entryCount) || a.scopeId.localeCompare(b.scopeId))[0];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
exports.FAM_ScopeMaintenance_Util = FAM_ScopeMaintenance_Util;
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_ScopeMaintenance_ControlService = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const mongoose_1 = tslib_1.__importDefault(require("mongoose"));
|
|
6
|
+
const error_codes_const_1 = require("../../../_collections/error-codes.const");
|
|
7
|
+
const fam_error_factory_util_1 = require("../../../_collections/fam-error-factory.util");
|
|
8
|
+
const embedding_1 = require("../../embedding");
|
|
9
|
+
const fam_reference_data_service_1 = require("./fam-reference.data-service");
|
|
10
|
+
const fam_scope_data_service_1 = require("./fam-scope.data-service");
|
|
11
|
+
const fam_scope_normalize_util_1 = require("../_collections/fam-scope-normalize.util");
|
|
12
|
+
const fam_scope_maintenance_util_1 = require("../_collections/fam-scope-maintenance.util");
|
|
13
|
+
/**
|
|
14
|
+
* `FAM_ScopeMaintenance_ControlService` (user-FR 2026-06-22) — **scope rename + merge** maintenance-engine.
|
|
15
|
+
* A claude-mem import (és egyéb) rendetlen scope-neveit (pl. `10.3.3` → valójában ccap-revisioned) rendezi:
|
|
16
|
+
* a `canonicalName`-t a denormalizált `scopePath[]`-en (MINDEN RAG-entry) + a reference `canonicalScopeRef`-jén
|
|
17
|
+
* + a scope-doc-on bulk-ban átírja (rename), VAGY egy scope-ot egy másikba olvaszt (merge → a forrás-scope
|
|
18
|
+
* minden entry-je az `into`-ra mutat, majd a forrás-scope soft-delete).
|
|
19
|
+
*
|
|
20
|
+
* **dryRun (default true a destruktív-biztonsághoz):** csak az érintett-számokat adja vissza, NEM ír. A
|
|
21
|
+
* tényleges íráshoz `dryRun:false` explicit. A bulk array-update mongoose `updateMany` + `arrayFilters` (a spec
|
|
22
|
+
* a tiszta `FAM_ScopeMaintenance_Util`-ból). A LVS-pool a Mongo-ból hidratál → a változás a következő (vagy
|
|
23
|
+
* scope-scoped) keresésnél él; nagy átírás után szerver-restart ajánlott a pool friss scope-metaadatához.
|
|
24
|
+
*
|
|
25
|
+
* **Megjegyzés (dedup):** a merge NEM dedup-ol — ha az `into` és a `from` ugyanazt a tartalmat tartalmazta,
|
|
26
|
+
* a `deduplicate_entries` (külön capability) felel a content-hash-összevonásért.
|
|
27
|
+
*/
|
|
28
|
+
class FAM_ScopeMaintenance_ControlService {
|
|
29
|
+
static _instance;
|
|
30
|
+
issuer = 'FAM_ScopeMaintenance_ControlService';
|
|
31
|
+
static getInstance() {
|
|
32
|
+
if (!FAM_ScopeMaintenance_ControlService._instance) {
|
|
33
|
+
FAM_ScopeMaintenance_ControlService._instance = new FAM_ScopeMaintenance_ControlService();
|
|
34
|
+
}
|
|
35
|
+
return FAM_ScopeMaintenance_ControlService._instance;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Egy scope ÁTNEVEZÉSE: a `scopeId` `canonicalName`-jét `newName`-re — a scopeId VÁLTOZATLAN, csak a
|
|
39
|
+
* denormalizált név (entry-k `scopePath` + reference + scope-doc). `dryRun` → csak az érintett-szám.
|
|
40
|
+
*/
|
|
41
|
+
async renameScope(set) {
|
|
42
|
+
const newName = (set.newName ?? '').trim();
|
|
43
|
+
if (!newName) {
|
|
44
|
+
throw this.invalid('A `newName` (nem-üres string) KÖTELEZŐ a rename_scope-hoz.', 'rename_scope');
|
|
45
|
+
}
|
|
46
|
+
const scope = await this.resolveScope(set);
|
|
47
|
+
const scopeId = scope._id;
|
|
48
|
+
const dryRun = set.dryRun !== false;
|
|
49
|
+
const affected = await this.countAffected(scopeId);
|
|
50
|
+
if (!dryRun) {
|
|
51
|
+
const entryUpdate = fam_scope_maintenance_util_1.FAM_ScopeMaintenance_Util.renameEntryUpdate(scopeId, newName);
|
|
52
|
+
await this.applyEntryUpdate(entryUpdate);
|
|
53
|
+
const refUpdate = fam_scope_maintenance_util_1.FAM_ScopeMaintenance_Util.renameReferenceUpdate(scopeId, newName);
|
|
54
|
+
await this.referenceCollection().collection.updateMany(refUpdate.filter, refUpdate.update);
|
|
55
|
+
// A scope-doc canonicalName-je ($set, atomikus — a denormalizált forrás).
|
|
56
|
+
await new fam_scope_data_service_1.FAM_Scope_DataService({ issuer: this.issuer })
|
|
57
|
+
.updateData({ filterBy: { _id: scopeId }, update: { $set: { canonicalName: newName } } });
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
operation: 'rename_scope',
|
|
61
|
+
dryRun: dryRun,
|
|
62
|
+
scope: { scopeId: scopeId, layer: scope.layer ?? '', oldName: scope.canonicalName ?? '', newName: newName },
|
|
63
|
+
affected: affected,
|
|
64
|
+
totalAffected: affected.reduce((sum, item) => sum + item.count, 0),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Két scope ÖSSZEOLVASZTÁSA: a `from` scope minden entry-je az `into` scope `{scopeId,layer,canonicalName}`-jére
|
|
69
|
+
* mutat, majd a `from` scope soft-delete. `dryRun` → csak az érintett-szám. Az `into` MARAD.
|
|
70
|
+
*/
|
|
71
|
+
async mergeScopes(set) {
|
|
72
|
+
const from = await this.resolveScope(set.from);
|
|
73
|
+
const into = await this.resolveScope(set.into);
|
|
74
|
+
const fromId = from._id;
|
|
75
|
+
const intoId = into._id;
|
|
76
|
+
if (fromId === intoId) {
|
|
77
|
+
throw this.invalid('A `from` és az `into` ugyanaz a scope — nincs mit összeolvasztani.', 'merge_scopes');
|
|
78
|
+
}
|
|
79
|
+
const dryRun = set.dryRun !== false;
|
|
80
|
+
const intoRef = { scopeId: intoId, layer: into.layer ?? '', canonicalName: into.canonicalName ?? '' };
|
|
81
|
+
const affected = await this.countAffected(fromId);
|
|
82
|
+
// MERGE-GUARD (2026-06-24 fix): külön projekt-gyökerek (`source.root`) → gyaníthatóan KÜLÖN projektek →
|
|
83
|
+
// BLOKK (force nélkül). Ez fogja meg a téves repoKey-duplikátum-merge-et (a `ccap-revisioned`→`ccap-speech-
|
|
84
|
+
// recognition` típusú szennyezést), MIELŐTT 50k fájl átkeveredne. A `force:true` (tudatos user-akció) felülírja.
|
|
85
|
+
const blockedReason = set.force
|
|
86
|
+
? undefined
|
|
87
|
+
: await this.crossProjectMergeBlock(fromId, intoId, from.canonicalName ?? '', into.canonicalName ?? '');
|
|
88
|
+
if (blockedReason) {
|
|
89
|
+
return {
|
|
90
|
+
operation: 'merge_scopes', dryRun: dryRun, blockedReason: blockedReason,
|
|
91
|
+
from: { scopeId: fromId, layer: from.layer ?? '', canonicalName: from.canonicalName ?? '' },
|
|
92
|
+
into: intoRef, affected: affected, totalAffected: affected.reduce((s, i) => s + i.count, 0),
|
|
93
|
+
fromScopeDeleted: false,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (!dryRun) {
|
|
97
|
+
const entryUpdate = fam_scope_maintenance_util_1.FAM_ScopeMaintenance_Util.mergeEntryUpdate(fromId, intoRef);
|
|
98
|
+
await this.applyEntryUpdate(entryUpdate);
|
|
99
|
+
const refUpdate = fam_scope_maintenance_util_1.FAM_ScopeMaintenance_Util.mergeReferenceUpdate(fromId, intoRef);
|
|
100
|
+
await this.referenceCollection().collection.updateMany(refUpdate.filter, refUpdate.update);
|
|
101
|
+
// A forrás-scope soft-delete (move-to-archive) — már nincs rá hivatkozó entry.
|
|
102
|
+
await new fam_scope_data_service_1.FAM_Scope_DataService({ issuer: this.issuer }).deleteData(fromId);
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
operation: 'merge_scopes',
|
|
106
|
+
dryRun: dryRun,
|
|
107
|
+
from: { scopeId: fromId, layer: from.layer ?? '', canonicalName: from.canonicalName ?? '' },
|
|
108
|
+
into: { scopeId: intoId, layer: into.layer ?? '', canonicalName: into.canonicalName ?? '' },
|
|
109
|
+
affected: affected,
|
|
110
|
+
totalAffected: affected.reduce((sum, item) => sum + item.count, 0),
|
|
111
|
+
fromScopeDeleted: !dryRun,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Egy scope SCAN-eredetű entry-jeinek soft-delete-je (RECOVERY egy félre-routolt scan után, user-FR 2026-06-24).
|
|
116
|
+
* A `sourceType` default `'scan'` → a manuális/agent/import-SZERZETT (pótolhatatlan) tartalmat SOHA nem törli.
|
|
117
|
+
* Az opcionális `sourceRootContains` a `source.root`-ot szűri (sebészi cross-root takarítás: pl. a `ccap-revisioned`
|
|
118
|
+
* gyökerű szennyezés a `ccap-client` scope-ból, a `ccap` saját entry-jeinek megtartásával). `dryRun` (DEFAULT true)
|
|
119
|
+
* → csak a per-collection szám + a `rootSamples` (verifikáció: jó scope-ot/gyökeret törlünk-e), NEM ír. A tényleges
|
|
120
|
+
* (soft-delete + LVS-pool-kivétel) az explicit `dryRun:false`. A scan-tartalom ÚJRA-SZKENNELHETŐ → nem destruktív-végleges.
|
|
121
|
+
*/
|
|
122
|
+
async resetScope(set) {
|
|
123
|
+
const scope = await this.resolveScope(set);
|
|
124
|
+
const scopeId = scope._id;
|
|
125
|
+
const dryRun = set.dryRun !== false;
|
|
126
|
+
const sourceType = (set.sourceType ?? 'scan').trim();
|
|
127
|
+
const sourceRootContains = (set.sourceRootContains ?? '').trim() || undefined;
|
|
128
|
+
const tableName = (set.table ?? '').trim() || undefined;
|
|
129
|
+
const filter = {
|
|
130
|
+
...fam_scope_maintenance_util_1.FAM_ScopeMaintenance_Util.buildResetFilter({ scopeId: scopeId, sourceType: sourceType, sourceRootContains: sourceRootContains }),
|
|
131
|
+
_deleted: null, // csak az AKTÍV (nem már soft-deleted) entry-ket (a `_deleted` Date-mező; `null` = aktív/hiányzó).
|
|
132
|
+
};
|
|
133
|
+
const targets = tableName
|
|
134
|
+
? (embedding_1.FAM_StoreRegistry_Util.getEntry(tableName) ? [embedding_1.FAM_StoreRegistry_Util.getEntry(tableName)] : [])
|
|
135
|
+
: embedding_1.FAM_STORE_REGISTRY;
|
|
136
|
+
const affected = [];
|
|
137
|
+
const rootSamples = new Set();
|
|
138
|
+
let totalAffected = 0;
|
|
139
|
+
for (const registryEntry of targets) {
|
|
140
|
+
// A példányosítás regisztrálja a modellt → a valós collection (a raw find a soft-delete-szemantikát explicit kezeli).
|
|
141
|
+
new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
|
|
142
|
+
const collection = mongoose_1.default.models[registryEntry.dataParams.dataName]?.collection;
|
|
143
|
+
if (!collection) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const matched = await collection.find(filter, { projection: { 'source.root': 1 } }).toArray();
|
|
147
|
+
for (const entry of matched) {
|
|
148
|
+
const root = entry.source?.root;
|
|
149
|
+
if (root && rootSamples.size < 8) {
|
|
150
|
+
rootSamples.add(root);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (!dryRun && matched.length) {
|
|
154
|
+
// BULK soft-delete (DIREKT `_deleted` — NEM `deleteData`): a FAM entry-tárak `addArchive:true`-ok, így a
|
|
155
|
+
// per-entry `deleteData` minden törölt entry-ből ARCHÍV-másolatot készítene (bloat + lassú 59k-ra). A reset
|
|
156
|
+
// DISCARD (nem archive) → közvetlen `_deleted` (Date), amit a `_deleted: null` aktív-szűrő kizár; a hívás
|
|
157
|
+
// utáni restart tiszta (a soft-deletedek nélküli) poolt hidratál. A live pool-coherence: in-memory removeVector.
|
|
158
|
+
await collection.updateMany(filter, { $set: { _deleted: new Date() } });
|
|
159
|
+
for (const entry of matched) {
|
|
160
|
+
embedding_1.FAM_VectorSearch_ControlService.getInstance().removeVector(registryEntry.table, String(entry._id));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
affected.push({ collection: collection.collectionName, count: matched.length });
|
|
164
|
+
totalAffected += matched.length;
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
operation: 'reset_scope',
|
|
168
|
+
dryRun: dryRun,
|
|
169
|
+
scope: { scopeId: scopeId, layer: scope.layer ?? '', canonicalName: scope.canonicalName ?? '' },
|
|
170
|
+
sourceType: sourceType,
|
|
171
|
+
sourceRootContains: sourceRootContains,
|
|
172
|
+
tableFilter: tableName,
|
|
173
|
+
affected: affected,
|
|
174
|
+
totalAffected: totalAffected,
|
|
175
|
+
rootSamples: [...rootSamples],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/** Az `entryUpdate` spec alkalmazása MINDEN fő RAG-tár collection-jén (mongoose `updateMany` + arrayFilters). */
|
|
179
|
+
async applyEntryUpdate(entryUpdate) {
|
|
180
|
+
for (const named of this.entryCollections()) {
|
|
181
|
+
await named.collection.updateMany(entryUpdate.filter, entryUpdate.update, { arrayFilters: entryUpdate.arrayFilters });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/** Egy scope entry-jeinek DISTINCT `source.root` halmaza minden fő tárban (a merge-guard bemenete). Normalizált (lowercase, `/`). */
|
|
185
|
+
async distinctRootsForScope(scopeId) {
|
|
186
|
+
const roots = new Set();
|
|
187
|
+
for (const named of this.entryCollections()) {
|
|
188
|
+
const values = await named.collection.distinct('source.root', { 'scopePath.scopeId': scopeId, _deleted: { $ne: true } });
|
|
189
|
+
for (const value of values) {
|
|
190
|
+
if (typeof value === 'string' && value.trim()) {
|
|
191
|
+
roots.add(value.trim().toLowerCase().replace(/\\/g, '/'));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return roots;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* MERGE-GUARD mag (2026-06-24 fix): blokk-ok-szöveg, ha a `from` és az `into` scope entry-jei KÜLÖNBÖZŐ (disjunkt)
|
|
199
|
+
* projekt-gyökerekből származnak — vagyis a merge KÜLÖN projekteket keverne. `undefined` (engedélyezve), ha bármelyik
|
|
200
|
+
* oldalon NINCS root-infó (manuális/importált entry — nem dönthető el), VAGY ha van KÖZÖS gyökér (ugyanaz a projekt,
|
|
201
|
+
* pl. átnevezés-merge legit). Ez fogja meg a téves repoKey-duplikátum-merge-et a tartalom-átkeveredés ELŐTT.
|
|
202
|
+
*/
|
|
203
|
+
async crossProjectMergeBlock(fromId, intoId, fromName, intoName) {
|
|
204
|
+
const [fromRoots, intoRoots] = await Promise.all([this.distinctRootsForScope(fromId), this.distinctRootsForScope(intoId)]);
|
|
205
|
+
if (!fromRoots.size || !intoRoots.size) {
|
|
206
|
+
return undefined; // nincs root-infó → nem blokkolunk (nem dönthető el biztosan)
|
|
207
|
+
}
|
|
208
|
+
const overlap = [...fromRoots].some((root) => intoRoots.has(root));
|
|
209
|
+
if (overlap) {
|
|
210
|
+
return undefined; // van közös projekt-gyökér → ugyanaz a projekt (legit merge)
|
|
211
|
+
}
|
|
212
|
+
const sample = (set) => [...set].slice(0, 3).join(', ');
|
|
213
|
+
return `A(z) '${fromName}' és '${intoName}' scope entry-jei KÜLÖNBÖZŐ projekt-gyökerekből származnak `
|
|
214
|
+
+ `(from: ${sample(fromRoots)} | into: ${sample(intoRoots)}) — gyaníthatóan KÜLÖN projektek, a merge `
|
|
215
|
+
+ `összekeverné a tartalmukat. Blokkolva (a téves repoKey-duplikátum-szennyezés ellen). `
|
|
216
|
+
+ `Ha tényleg össze akarod olvasztani: force:true.`;
|
|
217
|
+
}
|
|
218
|
+
/** A `scopeId`-t hivatkozó aktív entry-k száma per-collection (a 6 fő tár + a reference). */
|
|
219
|
+
/**
|
|
220
|
+
* Scope-integritás SELF-CHECK (user-FR 2026-06-22 — a rendszer ismerje fel magától a problémás scope-okat):
|
|
221
|
+
* (a) **árva projekt-scope** (`layer='project'`, de nincs `parentScopeId` — tipikusan import-eredetű lapos scope,
|
|
222
|
+
* a scan org-parented párja mellett), (b) **duplikátum** (ugyanaz a `(canonicalName, layer)` többször). Mindkettőhöz
|
|
223
|
+
* az entry-számot is adja (a merge-döntéshez: a parented/nagyobb az `into`). Csak OLVAS — a takarítás `merge_scopes`.
|
|
224
|
+
*/
|
|
225
|
+
async detectIssues() {
|
|
226
|
+
const scopes = await this.scopeCollection().collection
|
|
227
|
+
.find({}, { projection: { canonicalName: 1, layer: 1, parentScopeId: 1, canonicalRepoKey: 1 } })
|
|
228
|
+
.toArray();
|
|
229
|
+
// Az entry-számokat EGYSZER számoljuk minden érintett scope-ra (a 3 csoport megosztja — nincs dupla countDocuments).
|
|
230
|
+
const entryCountCache = new Map();
|
|
231
|
+
const countFor = async (scopeId) => {
|
|
232
|
+
if (!entryCountCache.has(scopeId)) {
|
|
233
|
+
entryCountCache.set(scopeId, await this.totalEntryCount(scopeId));
|
|
234
|
+
}
|
|
235
|
+
return entryCountCache.get(scopeId);
|
|
236
|
+
};
|
|
237
|
+
const orphanProjectScopes = [];
|
|
238
|
+
const byNameKey = new Map();
|
|
239
|
+
const byRepoKey = new Map();
|
|
240
|
+
const pushTo = (map, key, scope) => {
|
|
241
|
+
const bucket = map.get(key) ?? [];
|
|
242
|
+
bucket.push(scope);
|
|
243
|
+
map.set(key, bucket);
|
|
244
|
+
};
|
|
245
|
+
for (const scope of scopes) {
|
|
246
|
+
pushTo(byNameKey, `${scope.layer ?? ''}|${scope.canonicalName ?? ''}`, scope);
|
|
247
|
+
const repoKey = (scope.canonicalRepoKey ?? '').trim().toLowerCase();
|
|
248
|
+
if (repoKey) {
|
|
249
|
+
pushTo(byRepoKey, repoKey, scope);
|
|
250
|
+
}
|
|
251
|
+
if (scope.layer === 'project' && !scope.parentScopeId) {
|
|
252
|
+
orphanProjectScopes.push({
|
|
253
|
+
scopeId: String(scope._id), canonicalName: scope.canonicalName ?? '',
|
|
254
|
+
entryCount: await countFor(String(scope._id)),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const toMember = async (scope) => ({
|
|
259
|
+
scopeId: String(scope._id), parentScopeId: scope.parentScopeId,
|
|
260
|
+
canonicalName: scope.canonicalName ?? '', canonicalRepoKey: scope.canonicalRepoKey,
|
|
261
|
+
entryCount: await countFor(String(scope._id)),
|
|
262
|
+
});
|
|
263
|
+
const duplicateScopes = [];
|
|
264
|
+
for (const [, group] of byNameKey) {
|
|
265
|
+
if (group.length <= 1) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const members = [];
|
|
269
|
+
for (const scope of group) {
|
|
270
|
+
members.push(await toMember(scope));
|
|
271
|
+
}
|
|
272
|
+
duplicateScopes.push({ canonicalName: group[0].canonicalName ?? '', layer: group[0].layer ?? '', members: members });
|
|
273
|
+
}
|
|
274
|
+
const repoKeyDuplicates = [];
|
|
275
|
+
for (const [repoKey, group] of byRepoKey) {
|
|
276
|
+
// Egyedi scopeId-k (ugyanaz a scope ne számítson kétszer); ≥2 KÜLÖNBÖZŐ scope = repo-kulcs-duplikátum.
|
|
277
|
+
const uniqueIds = new Set(group.map((scope) => String(scope._id)));
|
|
278
|
+
if (uniqueIds.size <= 1) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const members = [];
|
|
282
|
+
for (const scope of group) {
|
|
283
|
+
members.push(await toMember(scope));
|
|
284
|
+
}
|
|
285
|
+
repoKeyDuplicates.push({ canonicalRepoKey: repoKey, members: members });
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
scopesScanned: scopes.length,
|
|
289
|
+
orphanProjectScopes: orphanProjectScopes,
|
|
290
|
+
duplicateScopes: duplicateScopes,
|
|
291
|
+
repoKeyDuplicates: repoKeyDuplicates,
|
|
292
|
+
hasIssues: orphanProjectScopes.length > 0 || duplicateScopes.length > 0 || repoKeyDuplicates.length > 0,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* `suggestMerges()` (scope-prevenció, user-FR 2026-06-22) — a `detectIssues` által felismert duplikátumokból
|
|
297
|
+
* KONKRÉT, futtatható `merge_scopes from→into` direktívák (pure `buildMergeSuggestions`). A repo-kulcs-csoportok
|
|
298
|
+
* elsőbbséget élveznek (a legbiztosabb identitás), az `into` a parented/legtöbb-entry-jű scope. NEM mutál — a
|
|
299
|
+
* tényleges összeolvasztás a `mergeScopes` (dryRun-default). Ez teszi az auto-felismerést AKCIONÁLHATÓVÁ.
|
|
300
|
+
*/
|
|
301
|
+
async suggestMerges() {
|
|
302
|
+
const report = await this.detectIssues();
|
|
303
|
+
const groups = [
|
|
304
|
+
...report.repoKeyDuplicates.map((duplicate) => ({
|
|
305
|
+
groupKind: 'repo-key', key: duplicate.canonicalRepoKey,
|
|
306
|
+
members: duplicate.members.map((member) => this.toMergeMember(member)),
|
|
307
|
+
})),
|
|
308
|
+
...report.duplicateScopes.map((duplicate) => ({
|
|
309
|
+
groupKind: 'name', key: `${duplicate.layer}|${duplicate.canonicalName}`,
|
|
310
|
+
members: duplicate.members.map((member) => this.toMergeMember(member, duplicate.canonicalName, duplicate.layer)),
|
|
311
|
+
})),
|
|
312
|
+
];
|
|
313
|
+
return { suggestions: fam_scope_maintenance_util_1.FAM_ScopeMaintenance_Util.buildMergeSuggestions(groups), report: report };
|
|
314
|
+
}
|
|
315
|
+
/** Egy issue-member → merge-member (a name-csoportban a közös név/layer pótlása, ha a member nem hordozza). */
|
|
316
|
+
toMergeMember(member, fallbackName, layer) {
|
|
317
|
+
return {
|
|
318
|
+
scopeId: member.scopeId,
|
|
319
|
+
canonicalName: member.canonicalName ?? fallbackName ?? '',
|
|
320
|
+
layer: layer,
|
|
321
|
+
parentScopeId: member.parentScopeId,
|
|
322
|
+
entryCount: member.entryCount,
|
|
323
|
+
canonicalRepoKey: member.canonicalRepoKey,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/** Egy scope összes entry-száma a 6 fő tárban (a detekció merge-súlyozásához). */
|
|
327
|
+
async totalEntryCount(scopeId) {
|
|
328
|
+
let total = 0;
|
|
329
|
+
for (const named of this.entryCollections()) {
|
|
330
|
+
total += await named.collection.countDocuments({ 'scopePath.scopeId': scopeId });
|
|
331
|
+
}
|
|
332
|
+
return total;
|
|
333
|
+
}
|
|
334
|
+
/** A `fam_scopes` mongoose-collection-je (a DS-példányosítás regisztrálja a modellt). */
|
|
335
|
+
scopeCollection() {
|
|
336
|
+
new fam_scope_data_service_1.FAM_Scope_DataService({ issuer: this.issuer });
|
|
337
|
+
const model = mongoose_1.default.models['fam_scopes'];
|
|
338
|
+
return { name: model?.collection?.collectionName ?? 'fam_scopes', collection: model.collection };
|
|
339
|
+
}
|
|
340
|
+
async countAffected(scopeId) {
|
|
341
|
+
const affected = [];
|
|
342
|
+
for (const named of this.entryCollections()) {
|
|
343
|
+
affected.push({ collection: named.name, count: await named.collection.countDocuments({ 'scopePath.scopeId': scopeId }) });
|
|
344
|
+
}
|
|
345
|
+
const reference = this.referenceCollection();
|
|
346
|
+
affected.push({ collection: reference.name, count: await reference.collection.countDocuments({ 'canonicalScopeRef.scopeId': scopeId }) });
|
|
347
|
+
return affected;
|
|
348
|
+
}
|
|
349
|
+
/** A 6 fő RAG-tár mongoose-collection-jei (a DS-példányosítás regisztrálja a modellt, onnan a valós collection). */
|
|
350
|
+
entryCollections() {
|
|
351
|
+
const result = [];
|
|
352
|
+
for (const entry of embedding_1.FAM_STORE_REGISTRY) {
|
|
353
|
+
new embedding_1.FAM_Entry_DataService({ dataParams: entry.dataParams, issuer: this.issuer });
|
|
354
|
+
const model = mongoose_1.default.models[entry.dataParams.dataName];
|
|
355
|
+
if (model?.collection) {
|
|
356
|
+
result.push({ name: model.collection.collectionName, collection: model.collection });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
/** A reference helper-tár mongoose-collection-je. */
|
|
362
|
+
referenceCollection() {
|
|
363
|
+
new fam_reference_data_service_1.FAM_Reference_DataService({ issuer: this.issuer });
|
|
364
|
+
const model = mongoose_1.default.models['fam_reference'];
|
|
365
|
+
return { name: model?.collection?.collectionName ?? 'fam_references', collection: model.collection };
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Egy scope feloldása a szelektorból: `scopeId` (közvetlen) VAGY `layer`+`canonicalName` (egyértelmű) VAGY
|
|
369
|
+
* csak `canonicalName` (minden layer-en, normalizált-egyezés — egyértelmű kell legyen). Nem-talált/kétértelmű → hiba.
|
|
370
|
+
*/
|
|
371
|
+
async resolveScope(selector) {
|
|
372
|
+
const dataService = new fam_scope_data_service_1.FAM_Scope_DataService({ issuer: this.issuer });
|
|
373
|
+
if (selector.scopeId) {
|
|
374
|
+
const scope = await dataService.findScopeById(selector.scopeId);
|
|
375
|
+
if (scope?._id) {
|
|
376
|
+
return scope;
|
|
377
|
+
}
|
|
378
|
+
throw this.notFound(`scopeId '${selector.scopeId}'`);
|
|
379
|
+
}
|
|
380
|
+
if (selector.canonicalName) {
|
|
381
|
+
const matches = selector.layer
|
|
382
|
+
? await dataService.findByCanonical(selector.layer, selector.canonicalName)
|
|
383
|
+
: await this.findByNameAnyLayer(dataService, selector.canonicalName);
|
|
384
|
+
if (matches.length === 1) {
|
|
385
|
+
return matches[0];
|
|
386
|
+
}
|
|
387
|
+
if (matches.length > 1) {
|
|
388
|
+
throw fam_error_factory_util_1.FAM_Error_Util.create({
|
|
389
|
+
errorCode: error_codes_const_1.FAM_ERROR_CODES.refResolveAmbiguous,
|
|
390
|
+
message: `A(z) '${selector.canonicalName}' név több scope-ra illeszkedik (${matches.length}) — adj meg `
|
|
391
|
+
+ '`layer`-t vagy `scopeId`-t az egyértelműsítéshez.',
|
|
392
|
+
issuer: this.issuer,
|
|
393
|
+
context: { operation: 'scope-maintenance:resolve' },
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
throw this.notFound(`'${selector.canonicalName}'${selector.layer ? ` (layer '${selector.layer}')` : ''}`);
|
|
397
|
+
}
|
|
398
|
+
throw this.invalid('Add meg a scope-ot: `scopeId` VAGY `canonicalName` (+ opcionális `layer`).', 'resolve-scope');
|
|
399
|
+
}
|
|
400
|
+
/** Egy név normalizált-egyezése MINDEN aktív scope-on (layer nélkül; a halmaz kicsi). */
|
|
401
|
+
async findByNameAnyLayer(dataService, name) {
|
|
402
|
+
const all = await dataService.findAllActive();
|
|
403
|
+
const target = fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(name);
|
|
404
|
+
return all.filter((scope) => fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(scope.canonicalName ?? '') === target);
|
|
405
|
+
}
|
|
406
|
+
notFound(what) {
|
|
407
|
+
return fam_error_factory_util_1.FAM_Error_Util.create({
|
|
408
|
+
errorCode: error_codes_const_1.FAM_ERROR_CODES.refResolveNoMatch,
|
|
409
|
+
message: `Nincs aktív scope: ${what}.`,
|
|
410
|
+
issuer: this.issuer,
|
|
411
|
+
context: { operation: 'scope-maintenance:resolve' },
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
invalid(message, operation) {
|
|
415
|
+
return fam_error_factory_util_1.FAM_Error_Util.create({
|
|
416
|
+
errorCode: error_codes_const_1.FAM_ERROR_CODES.mcpDispatchUnknownCapability,
|
|
417
|
+
message: message,
|
|
418
|
+
issuer: this.issuer,
|
|
419
|
+
context: { operation: `scope-maintenance:${operation}` },
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
exports.FAM_ScopeMaintenance_ControlService = FAM_ScopeMaintenance_ControlService;
|
|
@@ -409,7 +409,7 @@ class FAM_ScopeResolver_ControlService {
|
|
|
409
409
|
}
|
|
410
410
|
const scope_DS = new fam_scope_data_service_1.FAM_Scope_DataService({ issuer: this.issuer });
|
|
411
411
|
const scope = await scope_DS.findScopeById(scopeId);
|
|
412
|
-
return Boolean(scope
|
|
412
|
+
return Boolean(scope?._id);
|
|
413
413
|
}
|
|
414
414
|
// =========================================================================
|
|
415
415
|
// TRACE (dsgn-002 §4, dsgn-003 §4.2) — a debug-capability (MP-6) fogyasztja
|
|
@@ -460,7 +460,7 @@ class FAM_ScopeResolver_ControlService {
|
|
|
460
460
|
async traceEntityFallback(rawLayer, scopeDataService) {
|
|
461
461
|
const matches = await scopeDataService.findByCanonical(rawLayer.layer, rawLayer.rawName);
|
|
462
462
|
const hit = matches.find((scope) => !!scope._id);
|
|
463
|
-
if (hit
|
|
463
|
+
if (hit?._id) {
|
|
464
464
|
return { matched: true, scopeId: hit._id, canonicalName: hit.canonicalName };
|
|
465
465
|
}
|
|
466
466
|
return { matched: false };
|
|
@@ -211,5 +211,30 @@ class FAM_Scope_DataService extends nts_dynamo_1.DyNTS_DataService {
|
|
|
211
211
|
async setAliases(scopeId, aliases) {
|
|
212
212
|
await this.updateData({ filterBy: { _id: scopeId }, update: { $set: { aliases: aliases } } });
|
|
213
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* A scope `canonicalRepoKey` stamp-elése (scope-prevenció, user-FR 2026-06-22; `$set`, atomikus). A SCAN
|
|
216
|
+
* hívja a project-layer scope-ra a felismert git/package identitásból — erre épül a repo-kulcs alapú
|
|
217
|
+
* duplikátum-detekció. Csak nem-üres kulcsot ír; idempotens (ugyanaz a kulcs újraírása no-op-szerű).
|
|
218
|
+
*/
|
|
219
|
+
async setCanonicalRepoKey(scopeId, canonicalRepoKey) {
|
|
220
|
+
const key = (canonicalRepoKey ?? '').trim();
|
|
221
|
+
if (!scopeId || !key) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
await this.updateData({ filterBy: { _id: scopeId }, update: { $set: { canonicalRepoKey: key } } });
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* `findByRepoKey(canonicalRepoKey)` (scope-prevenció) — egy kanonikus repo-kulcsra illeszkedő összes aktív
|
|
228
|
+
* scope (case-insensitive trim-egyezés). ≥2 találat = repo-kulcs-duplikátum (a `detectIssues` merge-jelölt).
|
|
229
|
+
* A halmaz kicsi → in-memory szűrés.
|
|
230
|
+
*/
|
|
231
|
+
async findByRepoKey(canonicalRepoKey) {
|
|
232
|
+
const target = (canonicalRepoKey ?? '').trim().toLowerCase();
|
|
233
|
+
if (!target) {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
const all = await this.findAllActive();
|
|
237
|
+
return all.filter((scope) => (scope.canonicalRepoKey ?? '').trim().toLowerCase() === target);
|
|
238
|
+
}
|
|
214
239
|
}
|
|
215
240
|
exports.FAM_Scope_DataService = FAM_Scope_DataService;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* a fogyasztó-MP-ké. A reference/scope collection-ök MP-1-ben már a `dbModels`-ben vannak.
|
|
10
10
|
*/
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.FAM_FuzzyMatch_Util = exports.FAM_ScopeNormalize_Util = exports.FAM_ScopeResolver_ControlService = exports.FAM_Reference_DataService = exports.FAM_Scope_DataService = void 0;
|
|
12
|
+
exports.FAM_FuzzyMatch_Util = exports.FAM_ScopeNormalize_Util = exports.FAM_ScopeMaintenance_ControlService = exports.FAM_ScopeResolver_ControlService = exports.FAM_Reference_DataService = exports.FAM_Scope_DataService = void 0;
|
|
13
13
|
// SP-3.1 — Scope CRUD + scope-fa traverzál
|
|
14
14
|
var fam_scope_data_service_1 = require("./_services/fam-scope.data-service");
|
|
15
15
|
Object.defineProperty(exports, "FAM_Scope_DataService", { enumerable: true, get: function () { return fam_scope_data_service_1.FAM_Scope_DataService; } });
|
|
@@ -19,6 +19,9 @@ Object.defineProperty(exports, "FAM_Reference_DataService", { enumerable: true,
|
|
|
19
19
|
// SP-3.3/SP-3.4 — a feloldó control-service (write-resolve / read-expand / cache / scopeExists / uncertainty)
|
|
20
20
|
var fam_scope_resolver_control_service_1 = require("./_services/fam-scope-resolver.control-service");
|
|
21
21
|
Object.defineProperty(exports, "FAM_ScopeResolver_ControlService", { enumerable: true, get: function () { return fam_scope_resolver_control_service_1.FAM_ScopeResolver_ControlService; } });
|
|
22
|
+
// user-FR 2026-06-22 — scope rename/merge maintenance (rendetlen claude-mem import scope-nevek rendezése)
|
|
23
|
+
var fam_scope_maintenance_control_service_1 = require("./_services/fam-scope-maintenance.control-service");
|
|
24
|
+
Object.defineProperty(exports, "FAM_ScopeMaintenance_ControlService", { enumerable: true, get: function () { return fam_scope_maintenance_control_service_1.FAM_ScopeMaintenance_ControlService; } });
|
|
22
25
|
// utils (SP-3.1/SP-3.2)
|
|
23
26
|
var fam_scope_normalize_util_1 = require("./_collections/fam-scope-normalize.util");
|
|
24
27
|
Object.defineProperty(exports, "FAM_ScopeNormalize_Util", { enumerable: true, get: function () { return fam_scope_normalize_util_1.FAM_ScopeNormalize_Util; } });
|