@futdevpro/fdp-agent-memory 1.1.30 → 1.1.113
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 +160 -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 +216 -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
package/build/src/_modules/scope-reference/_services/fam-scope-maintenance.control-service.js
CHANGED
|
@@ -79,6 +79,20 @@ class FAM_ScopeMaintenance_ControlService {
|
|
|
79
79
|
const dryRun = set.dryRun !== false;
|
|
80
80
|
const intoRef = { scopeId: intoId, layer: into.layer ?? '', canonicalName: into.canonicalName ?? '' };
|
|
81
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
|
+
}
|
|
82
96
|
if (!dryRun) {
|
|
83
97
|
const entryUpdate = fam_scope_maintenance_util_1.FAM_ScopeMaintenance_Util.mergeEntryUpdate(fromId, intoRef);
|
|
84
98
|
await this.applyEntryUpdate(entryUpdate);
|
|
@@ -97,13 +111,232 @@ class FAM_ScopeMaintenance_ControlService {
|
|
|
97
111
|
fromScopeDeleted: !dryRun,
|
|
98
112
|
};
|
|
99
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
|
+
}
|
|
100
178
|
/** Az `entryUpdate` spec alkalmazása MINDEN fő RAG-tár collection-jén (mongoose `updateMany` + arrayFilters). */
|
|
101
179
|
async applyEntryUpdate(entryUpdate) {
|
|
102
180
|
for (const named of this.entryCollections()) {
|
|
103
181
|
await named.collection.updateMany(entryUpdate.filter, entryUpdate.update, { arrayFilters: entryUpdate.arrayFilters });
|
|
104
182
|
}
|
|
105
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
|
+
}
|
|
106
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
|
+
}
|
|
107
340
|
async countAffected(scopeId) {
|
|
108
341
|
const affected = [];
|
|
109
342
|
for (const named of this.entryCollections()) {
|
|
@@ -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,10 +9,18 @@ const nts_dynamo_2 = require("@futdevpro/nts-dynamo");
|
|
|
9
9
|
const fam_version_const_1 = require("../../../_collections/fam-version.const");
|
|
10
10
|
const fam_table_type_enum_1 = require("../../../_enums/fam-table.type-enum");
|
|
11
11
|
const fam_config_level_type_enum_1 = require("../../../_enums/fam-config-level.type-enum");
|
|
12
|
+
const fam_operation_queue_service_1 = require("../../../_collections/fam-operation-queue.service");
|
|
13
|
+
const fam_scan_job_control_service_1 = require("../../../_modules/ingest/_services/fam-scan-job.control-service");
|
|
14
|
+
const fam_vector_search_control_service_1 = require("../../../_modules/embedding/_services/fam-vector-search.control-service");
|
|
12
15
|
const config_control_service_1 = require("../config/config.control-service");
|
|
16
|
+
const errors_control_service_1 = require("../errors/errors.control-service");
|
|
17
|
+
const fam_error_data_model_1 = require("../../../_models/data-models/fam-error.data-model");
|
|
13
18
|
const embedding_1 = require("../../../_modules/embedding");
|
|
14
19
|
const ingest_1 = require("../../../_modules/ingest");
|
|
15
20
|
const mcp_1 = require("../../../_modules/mcp");
|
|
21
|
+
const fam_doctor_control_service_1 = require("./fam-doctor.control-service");
|
|
22
|
+
const retrieval_1 = require("../../../_modules/retrieval");
|
|
23
|
+
const fam_request_origin_util_1 = require("../../../_collections/fam-request-origin.util");
|
|
16
24
|
/** A UI/CLI lapozás CCAP-örökség konstansai (dsgn-010/dsgn-011 §4.1). */
|
|
17
25
|
const DEFAULT_PAGE_SIZE = 50;
|
|
18
26
|
const MAX_PAGE_SIZE = 500;
|
|
@@ -53,6 +61,9 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
|
|
|
53
61
|
this.writeEndpoint(),
|
|
54
62
|
this.capabilitiesEndpoint(),
|
|
55
63
|
this.healthEndpoint(),
|
|
64
|
+
this.doctorEndpoint(),
|
|
65
|
+
this.rulesInjectEndpoint(),
|
|
66
|
+
this.rulesPropagateEndpoint(),
|
|
56
67
|
this.itemsEndpoint(),
|
|
57
68
|
this.statsEndpoint(),
|
|
58
69
|
this.statsTableEndpoint(),
|
|
@@ -60,6 +71,12 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
|
|
|
60
71
|
this.duplicatesEndpoint(),
|
|
61
72
|
this.configGetEndpoint(),
|
|
62
73
|
this.configSetEndpoint(),
|
|
74
|
+
this.scanStartEndpoint(),
|
|
75
|
+
this.scanStatusEndpoint(),
|
|
76
|
+
this.scanJobsEndpoint(),
|
|
77
|
+
this.scanCancelEndpoint(),
|
|
78
|
+
this.errorsSummaryEndpoint(),
|
|
79
|
+
this.errorsEndpoint(),
|
|
63
80
|
];
|
|
64
81
|
}
|
|
65
82
|
// =========================================================================
|
|
@@ -110,6 +127,14 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
|
|
|
110
127
|
// Heap-load-shed (OOM-hibakezelés): a NEHÉZ write-eket (scan/import/re-embed) critical
|
|
111
128
|
// memória-nyomásnál elutasítjuk (503), MIELŐTT egy nagy allokáció átlökné a plafont.
|
|
112
129
|
this.assertHeapCapacity(String(input.operation));
|
|
130
|
+
// FEAT-008 (user-FR 2026-06-22): a NEHÉZ write-ek SZERVER-OLDALI QUEUE-n mennek át → a szerver
|
|
131
|
+
// szerializálja őket (egyszerre egy), így több agent egyidejű write-jai sorba rakódnak, és nincs
|
|
132
|
+
// párhuzamos scan-feldolgozás (a war-factory/warbots átfedés gyökér-megoldása). A könnyű ops
|
|
133
|
+
// (create/update/delete) bypass-olják a queue-t. Lásd `FAM_OperationQueue_Service`.
|
|
134
|
+
if (Api_Controller.HEAVY_WRITE_OPERATIONS.includes(String(input.operation))) {
|
|
135
|
+
return fam_operation_queue_service_1.FAM_OperationQueue_Service.getInstance()
|
|
136
|
+
.enqueue(() => mcp_1.FAM_WriteTool_Service.getInstance().handle(input));
|
|
137
|
+
}
|
|
113
138
|
return mcp_1.FAM_WriteTool_Service.getInstance().handle(input);
|
|
114
139
|
});
|
|
115
140
|
},
|
|
@@ -132,9 +157,235 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
|
|
|
132
157
|
],
|
|
133
158
|
});
|
|
134
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* `POST /scan/start` (FEAT-008) — szerver-oldali multi-projekt scan-job INDÍTÁSA. Body: explicit `projects` VAGY
|
|
162
|
+
* `root` (felderítés a SZERVEREN) + opcionális cooldown-override. AZONNAL visszaad `{ jobId, total }` (a hosszú
|
|
163
|
+
* feldolgozás háttérben fut → nincs Node-requestTimeout). A progress a `GET /scan/status/:jobId`-on.
|
|
164
|
+
*/
|
|
165
|
+
scanStartEndpoint() {
|
|
166
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
167
|
+
name: 'scanStart',
|
|
168
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.post,
|
|
169
|
+
endpoint: '/scan/start',
|
|
170
|
+
tasks: [
|
|
171
|
+
async (req, res) => {
|
|
172
|
+
await this.run(res, async () => {
|
|
173
|
+
const input = (req.body ?? {});
|
|
174
|
+
// Aláírás-feloldás (user-FR 2026-06-25): a `X-FAM-*` header VAGY a body `origin` → KI indította
|
|
175
|
+
// a scant (több egyidejű workspace-session beazonosítható). A job-on + a lista-nézetben látszik.
|
|
176
|
+
input.origin = fam_request_origin_util_1.FAM_RequestOrigin_Util.fromRequest(req, input.origin);
|
|
177
|
+
return fam_scan_job_control_service_1.FAM_ScanJob_ControlService.getInstance().start(input);
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/** `GET /scan/status/:jobId` (FEAT-008) — egy scan-job állapota (progress + per-projekt eredmények). 404, ha ismeretlen. */
|
|
184
|
+
scanStatusEndpoint() {
|
|
185
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
186
|
+
name: 'scanStatus',
|
|
187
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.get,
|
|
188
|
+
endpoint: '/scan/status/:jobId',
|
|
189
|
+
tasks: [
|
|
190
|
+
async (req, res) => {
|
|
191
|
+
await this.run(res, async () => {
|
|
192
|
+
const job = fam_scan_job_control_service_1.FAM_ScanJob_ControlService.getInstance().getStatus(String(req.params.jobId));
|
|
193
|
+
if (!job) {
|
|
194
|
+
throw new fsm_dynamo_1.DyFM_Error({
|
|
195
|
+
errorCode: 'FAM-SCAN-JOB-404',
|
|
196
|
+
message: `Ismeretlen scan-job: '${String(req.params.jobId)}' (lehet, hogy a szerver `
|
|
197
|
+
+ 'újraindult — a job-ok in-memory tárolódnak).',
|
|
198
|
+
issuer: this.issuer,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return job;
|
|
202
|
+
});
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* `GET /scan/jobs` (FEAT-008 Phase 5 — „jobb job kezelés") — AZ ÖSSZES (futó + befejezett) scan-job KOMPAKT listája,
|
|
209
|
+
* legfrissebb elöl. Egy elárvult/futó job a `jobId` ISMERETE NÉLKÜL is felfedezhető (+ a stall-jelzés is látszik).
|
|
210
|
+
*/
|
|
211
|
+
scanJobsEndpoint() {
|
|
212
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
213
|
+
name: 'scanJobs',
|
|
214
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.get,
|
|
215
|
+
endpoint: '/scan/jobs',
|
|
216
|
+
tasks: [
|
|
217
|
+
async (req, res) => {
|
|
218
|
+
await this.run(res, async () => ({ jobs: fam_scan_job_control_service_1.FAM_ScanJob_ControlService.getInstance().listJobs() }));
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* `POST /scan/cancel/:jobId` (FEAT-008 Phase 5) — egy FUTÓ scan-job megszakítás-kérése (a processor a következő projekt
|
|
225
|
+
* ELŐTT ellenőrzi). `{ cancelled: true }`, ha a job létezett + futott; `false`, ha ismeretlen/már terminál.
|
|
226
|
+
*/
|
|
227
|
+
scanCancelEndpoint() {
|
|
228
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
229
|
+
name: 'scanCancel',
|
|
230
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.post,
|
|
231
|
+
endpoint: '/scan/cancel/:jobId',
|
|
232
|
+
tasks: [
|
|
233
|
+
async (req, res) => {
|
|
234
|
+
await this.run(res, async () => ({
|
|
235
|
+
cancelled: fam_scan_job_control_service_1.FAM_ScanJob_ControlService.getInstance().cancel(String(req.params.jobId)),
|
|
236
|
+
}));
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* `GET /errors` (→ `/api/errors`, user-FR 2026-06-24: „kéne legyenek végpontok, amivel le tudjuk érni az errorokat")
|
|
243
|
+
* — a `fam_errors` rekordok KOMPAKT, FAM-natív nézete a `/api` felületen (a built-in `/api/server/errors` nyers/bő
|
|
244
|
+
* Dynamo-objektumai helyett). Query: `code` (errorCode-részlet, case-insensitive), `unhandledOnly` (`true` → csak nem
|
|
245
|
+
* kezeltek), `limit` (default 50, max 500). Sorrend: occurrence DESC, majd recency. A `contextSnapshot` (benne a
|
|
246
|
+
* `failedSample` + provider/table/operation) is kijön — így az LM-Studio-omlasztó tartalom-séma diagnosztizálható.
|
|
247
|
+
*/
|
|
248
|
+
errorsEndpoint() {
|
|
249
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
250
|
+
name: 'errorsList',
|
|
251
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.get,
|
|
252
|
+
endpoint: '/errors',
|
|
253
|
+
tasks: [
|
|
254
|
+
async (req, res) => {
|
|
255
|
+
await this.run(res, async () => {
|
|
256
|
+
const unhandledOnly = req.query.unhandledOnly === 'true';
|
|
257
|
+
const codeFilter = req.query.code ? String(req.query.code).toLowerCase() : '';
|
|
258
|
+
const limit = Math.min(500, Math.max(1, Number(req.query.limit) || 50));
|
|
259
|
+
const all = await errors_control_service_1.FAM_Error_ControlService.getInstance()
|
|
260
|
+
.listErrors(unhandledOnly ? { handled: false } : undefined);
|
|
261
|
+
const filtered = all
|
|
262
|
+
.filter((r) => !codeFilter || (r.errorCode ?? '').toLowerCase().includes(codeFilter))
|
|
263
|
+
.sort((a, b) => fam_error_data_model_1.FAM_Error_DataModel.occurrenceCount(b) - fam_error_data_model_1.FAM_Error_DataModel.occurrenceCount(a)
|
|
264
|
+
|| String(b._id ?? '').localeCompare(String(a._id ?? '')));
|
|
265
|
+
return {
|
|
266
|
+
total: filtered.length,
|
|
267
|
+
returned: Math.min(limit, filtered.length),
|
|
268
|
+
errors: filtered.slice(0, limit).map((r) => this.mapErrorCompact(r)),
|
|
269
|
+
};
|
|
270
|
+
});
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* `GET /errors/summary` (→ `/api/errors/summary`) — errorCode szerint CSOPORTOSÍTOTT triage-nézet: kódonként az
|
|
277
|
+
* occurrence-összeg + distinct rekordszám + utolsó üzenet + egy reprezentatív `failedSample`. Gyors „mi omlik a
|
|
278
|
+
* legtöbbet" áttekintés (pl. melyik `FAM-EMB-PROVIDER-002` séma viszi el az LM Studio-t).
|
|
279
|
+
*/
|
|
280
|
+
errorsSummaryEndpoint() {
|
|
281
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
282
|
+
name: 'errorsSummary',
|
|
283
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.get,
|
|
284
|
+
endpoint: '/errors/summary',
|
|
285
|
+
tasks: [
|
|
286
|
+
async (req, res) => {
|
|
287
|
+
await this.run(res, async () => {
|
|
288
|
+
const all = await errors_control_service_1.FAM_Error_ControlService.getInstance().listErrors();
|
|
289
|
+
const groups = {};
|
|
290
|
+
for (const r of all) {
|
|
291
|
+
const code = r.errorCode ?? '(no-code)';
|
|
292
|
+
const group = groups[code] ?? { errorCode: code, occurrences: 0, records: 0 };
|
|
293
|
+
group.occurrences += fam_error_data_model_1.FAM_Error_DataModel.occurrenceCount(r);
|
|
294
|
+
group.records += 1;
|
|
295
|
+
group.lastMessage = r.message ?? group.lastMessage;
|
|
296
|
+
const sample = r.contextSnapshot?.failedSample;
|
|
297
|
+
if (sample && !group.sampleFailedSample) {
|
|
298
|
+
group.sampleFailedSample = String(sample).slice(0, 400);
|
|
299
|
+
}
|
|
300
|
+
groups[code] = group;
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
groups: Object.values(groups).sort((a, b) => b.occurrences - a.occurrences),
|
|
304
|
+
};
|
|
305
|
+
});
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
/** Egy `fam_errors` rekord → KOMPAKT, FAM-natív hiba-nézet (a nyers Dynamo-mezők/óriás-stack helyett a diagnózishoz kell). */
|
|
311
|
+
mapErrorCompact(record) {
|
|
312
|
+
return {
|
|
313
|
+
id: String(record._id ?? ''),
|
|
314
|
+
errorCode: record.errorCode,
|
|
315
|
+
level: typeof Reflect.get(record, 'level') === 'string' ? String(Reflect.get(record, 'level')) : undefined,
|
|
316
|
+
message: record.message,
|
|
317
|
+
occurrenceCount: fam_error_data_model_1.FAM_Error_DataModel.occurrenceCount(record),
|
|
318
|
+
handled: record.handled ?? false,
|
|
319
|
+
issuer: record.issuer,
|
|
320
|
+
context: record.contextSnapshot,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
135
323
|
// =========================================================================
|
|
136
324
|
// /health — CLI remote reachability-check (dsgn-010 §9)
|
|
137
325
|
// =========================================================================
|
|
326
|
+
/**
|
|
327
|
+
* `GET /doctor` (#2 diagnosztikai végpont, user-FR 2026-06-24) — SZERVER-OLDALI aggregált health-report
|
|
328
|
+
* (env/mongodb/config/embedding/vector/collections + scope-integritás), a `fam doctor` REST-tükre (ADR-015
|
|
329
|
+
* server-centric: a CLI/kliens/UI/külső eszköz EZT hívja, nem 6 külön capability-t). Opcionális `?skip=embedding,vector`.
|
|
330
|
+
*/
|
|
331
|
+
doctorEndpoint() {
|
|
332
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
333
|
+
name: 'doctor',
|
|
334
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.get,
|
|
335
|
+
endpoint: '/doctor',
|
|
336
|
+
tasks: [
|
|
337
|
+
async (req, res) => {
|
|
338
|
+
await this.run(res, async () => {
|
|
339
|
+
const skipRaw = typeof req.query.skip === 'string' ? req.query.skip : '';
|
|
340
|
+
const skip = skipRaw.split(',').map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
341
|
+
return fam_doctor_control_service_1.FAM_Doctor_ControlService.getInstance().runDoctor({ skip: skip });
|
|
342
|
+
});
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* `POST /rules/inject` (Rules-Management MP-2) — a SessionStart-hook (+ a `read`-reflex) szerver-oldali rule-assembly-je:
|
|
349
|
+
* a `cwd`-ből (vagy explicit felülírásból) detektált tier-kontextusra `importance`-rangsorolt, char-budgetbe illesztett
|
|
350
|
+
* always-apply rule-blokk a „(…and N more)" záró sorral. A hook DUMB marad (csak `additionalContext`-be teszi a `block`-ot).
|
|
351
|
+
* Body: `{ cwd?, isFdp?, inProject?, projectType?, topics?, budgetChars? }`.
|
|
352
|
+
*/
|
|
353
|
+
rulesInjectEndpoint() {
|
|
354
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
355
|
+
name: 'rulesInject',
|
|
356
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.post,
|
|
357
|
+
endpoint: '/rules/inject',
|
|
358
|
+
tasks: [
|
|
359
|
+
async (req, res) => {
|
|
360
|
+
await this.run(res, async () => {
|
|
361
|
+
const input = (req.body ?? {});
|
|
362
|
+
return retrieval_1.FAM_RuleInjection_ControlService.getInstance().assembleInjection(input);
|
|
363
|
+
});
|
|
364
|
+
},
|
|
365
|
+
],
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* `POST /rules/propagate` (Rules-Management MP-5) — a kontextusra érvényes szabályok GENERÁLT, marker-keretezett
|
|
370
|
+
* `CLAUDE.md`-blokkja (propagáció a FAM-NÉLKÜLI projekt-agenteknek). Budget nélkül (minden alkalmazható szabály),
|
|
371
|
+
* dedup `ruleId`/`derivedFrom` szerint. A hívó (sync-tool/CLI) a `block`-ot a projekt-`CLAUDE.md` markerei közé illeszti.
|
|
372
|
+
* Body: `{ cwd?, isFdp?, inProject?, projectType?, topics? }`.
|
|
373
|
+
*/
|
|
374
|
+
rulesPropagateEndpoint() {
|
|
375
|
+
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
376
|
+
name: 'rulesPropagate',
|
|
377
|
+
type: fsm_dynamo_1.DyFM_HttpCallType.post,
|
|
378
|
+
endpoint: '/rules/propagate',
|
|
379
|
+
tasks: [
|
|
380
|
+
async (req, res) => {
|
|
381
|
+
await this.run(res, async () => {
|
|
382
|
+
const input = (req.body ?? {});
|
|
383
|
+
return retrieval_1.FAM_RuleInjection_ControlService.getInstance().assemblePropagation(input);
|
|
384
|
+
});
|
|
385
|
+
},
|
|
386
|
+
],
|
|
387
|
+
});
|
|
388
|
+
}
|
|
138
389
|
/** `GET /health` — a CLI `--server-url` elérhetőség-próbája (dsgn-010 §9). */
|
|
139
390
|
healthEndpoint() {
|
|
140
391
|
return new nts_dynamo_1.DyNTS_Endpoint_Params({
|
|
@@ -145,10 +396,17 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
|
|
|
145
396
|
async (req, res) => {
|
|
146
397
|
// A heap-nyomás IS kimegy a health-en (observability) — a BEDROCK DyNTS_MemoryGuard pressure-éből.
|
|
147
398
|
const memory = nts_dynamo_2.DyNTS_MemoryGuard.getInstance().pressure();
|
|
399
|
+
// Hidratálás-állapot (user-FR 2026-06-22): a boot-hidratálás időbe telik → a hívó lássa, kész-e.
|
|
400
|
+
const hydration = fam_vector_search_control_service_1.FAM_VectorSearch_ControlService.getInstance().hydrationSummary();
|
|
401
|
+
// Readiness-coordinator per-tár állapot (pending/hydrating/ready/failed) — a read-queue diagnosztikája.
|
|
402
|
+
const readiness = embedding_1.FAM_HydrationCoordinator_ControlService.getInstance().snapshot();
|
|
148
403
|
res.send({
|
|
149
404
|
ok: true, service: 'fdp-agent-memory', version: fam_version_const_1.FAM_VERSION, uptime: process.uptime(),
|
|
405
|
+
ready: hydration.complete,
|
|
150
406
|
memory: { level: memory.level, heapUsedMb: memory.heapUsedMb, heapLimitMb: memory.heapLimitMb,
|
|
151
407
|
heapPct: memory.heapPct, rssMb: memory.rssMb, gcFraction: memory.gcFraction },
|
|
408
|
+
hydration: hydration,
|
|
409
|
+
readiness: readiness,
|
|
152
410
|
});
|
|
153
411
|
},
|
|
154
412
|
],
|
|
@@ -344,12 +602,24 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
|
|
|
344
602
|
async (req, res) => {
|
|
345
603
|
await this.run(res, async () => {
|
|
346
604
|
const table = this.parseTable(req.params.table);
|
|
605
|
+
// A vessző-szeparált lista-paraméterek (projects/excludeProjects) tömbbé bontása.
|
|
606
|
+
const parseList = (value) => (typeof value === 'string' && value.length
|
|
607
|
+
? value.split(',').map((item) => item.trim()).filter((item) => item.length > 0)
|
|
608
|
+
: undefined);
|
|
347
609
|
return embedding_1.FAM_DuplicateScan_ControlService.getInstance().scan({
|
|
348
610
|
table: table,
|
|
349
611
|
threshold: req.query.threshold !== undefined ? Number(req.query.threshold) : undefined,
|
|
350
612
|
neighbors: req.query.neighbors !== undefined ? this.parseNumber(req.query.neighbors, 10) : undefined,
|
|
351
613
|
maxEntries: req.query.maxEntries !== undefined ? this.parseNumber(req.query.maxEntries, 3000) : undefined,
|
|
352
614
|
maxClusters: req.query.maxClusters !== undefined ? this.parseNumber(req.query.maxClusters, 100) : undefined,
|
|
615
|
+
pathRegex: typeof req.query.pathRegex === 'string' && req.query.pathRegex.length ? req.query.pathRegex : undefined,
|
|
616
|
+
excludePathRegex: typeof req.query.excludePathRegex === 'string' && req.query.excludePathRegex.length
|
|
617
|
+
? req.query.excludePathRegex : undefined,
|
|
618
|
+
excludeRootRegex: typeof req.query.excludeRootRegex === 'string' && req.query.excludeRootRegex.length
|
|
619
|
+
? req.query.excludeRootRegex : undefined,
|
|
620
|
+
projects: parseList(req.query.projects),
|
|
621
|
+
excludeProjects: parseList(req.query.excludeProjects),
|
|
622
|
+
minProjects: req.query.minProjects !== undefined ? this.parseNumber(req.query.minProjects, 0) : undefined,
|
|
353
623
|
});
|
|
354
624
|
});
|
|
355
625
|
},
|