@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.
Files changed (89) hide show
  1. package/build/package.json +2 -2
  2. package/build/src/_cli/_commands/find-duplicates.command.js +41 -16
  3. package/build/src/_cli/_commands/scan-projects.command.js +113 -113
  4. package/build/src/_cli/_commands/scan.command.js +2 -0
  5. package/build/src/_cli/_commands/serve.command.js +110 -10
  6. package/build/src/_cli/_services/fam-client.service.js +25 -1
  7. package/build/src/_cli/register-commands.js +28 -0
  8. package/build/src/_collections/config-catalog.const.js +152 -33
  9. package/build/src/_collections/error-codes.const.js +2 -0
  10. package/build/src/_collections/fam-console.util.js +38 -260
  11. package/build/src/_collections/fam-db-models.const.js +2 -0
  12. package/build/src/_collections/fam-entry-bootstrap.util.js +20 -6
  13. package/build/src/_collections/fam-error-context.util.js +1 -0
  14. package/build/src/_collections/fam-mcp-bridge.util.js +15 -1
  15. package/build/src/_collections/fam-operation-queue.service.js +85 -0
  16. package/build/src/_collections/fam-project-discovery.util.js +148 -0
  17. package/build/src/_collections/fam-request-origin.util.js +41 -0
  18. package/build/src/_collections/fam-retry.util.js +48 -0
  19. package/build/src/_collections/fam-scan-progress-sink.service.js +26 -0
  20. package/build/src/_collections/title.const.js +26 -26
  21. package/build/src/_enums/fam-rule-scope.type-enum.js +25 -0
  22. package/build/src/_models/data-models/fam-entry.data-model.js +29 -0
  23. package/build/src/_models/data-models/fam-memory.data-model.js +5 -0
  24. package/build/src/_models/data-models/fam-rules.data-model.js +24 -0
  25. package/build/src/_models/data-models/fam-scan-job.data-model.js +73 -0
  26. package/build/src/_models/data-models/fam-scope.data-model.js +9 -0
  27. package/build/src/_modules/capture/_services/fam-auto-capture.control-service.js +1 -1
  28. package/build/src/_modules/embedding/_services/fam-dedup-warn.control-service.js +40 -0
  29. package/build/src/_modules/embedding/_services/fam-duplicate-scan.control-service.js +74 -16
  30. package/build/src/_modules/embedding/_services/fam-embedding-bootstrap.control-service.js +50 -0
  31. package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +32 -6
  32. package/build/src/_modules/embedding/_services/fam-embedding.control-service.js +142 -9
  33. package/build/src/_modules/embedding/_services/fam-entry.data-service.js +32 -1
  34. package/build/src/_modules/embedding/_services/fam-hydration-coordinator.control-service.js +159 -0
  35. package/build/src/_modules/embedding/_services/fam-lmstudio-embedding.provider.js +41 -4
  36. package/build/src/_modules/embedding/_services/fam-vector-search.control-service.js +67 -12
  37. package/build/src/_modules/embedding/index.js +5 -1
  38. package/build/src/_modules/ingest/_collections/fam-file-routing.util.js +10 -0
  39. package/build/src/_modules/ingest/_collections/fam-frontmatter.util.js +134 -0
  40. package/build/src/_modules/ingest/_collections/fam-project-identity.util.js +30 -0
  41. package/build/src/_modules/ingest/_collections/fam-scan-progress.util.js +5 -2
  42. package/build/src/_modules/ingest/_collections/fam-scan-summary.util.js +55 -29
  43. package/build/src/_modules/ingest/_collections/fam-split-chunker.util.js +139 -0
  44. package/build/src/_modules/ingest/_models/interfaces/fam-scan-job.interface.js +2 -0
  45. package/build/src/_modules/ingest/_services/fam-chunker.control-service.js +10 -0
  46. package/build/src/_modules/ingest/_services/fam-delta-compare.util.js +68 -1
  47. package/build/src/_modules/ingest/_services/fam-ingest-run.data-service.js +1 -1
  48. package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +177 -37
  49. package/build/src/_modules/ingest/_services/fam-scan-job.control-service.js +429 -0
  50. package/build/src/_modules/ingest/_services/fam-scan-job.data-service.js +34 -0
  51. package/build/src/_modules/ingest/_services/fam-scan.control-service.js +6 -4
  52. package/build/src/_modules/ingest/index.js +6 -1
  53. package/build/src/_modules/mcp/_collections/fam-core-tools.const.js +1 -1
  54. package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +264 -41
  55. package/build/src/_modules/mcp/_services/fam-read-tool.service.js +71 -1
  56. package/build/src/_modules/mcp/_services/fam-write-tool.service.js +36 -0
  57. package/build/src/_modules/migration/_services/fam-claude-mem-import.control-service.js +1 -1
  58. package/build/src/_modules/retrieval/_collections/fam-lexical-match.util.js +69 -0
  59. package/build/src/_modules/retrieval/_collections/fam-memory-activation.util.js +66 -0
  60. package/build/src/_modules/retrieval/_collections/fam-rule-injection.util.js +125 -0
  61. package/build/src/_modules/retrieval/_collections/fam-rule-propagation.util.js +95 -0
  62. package/build/src/_modules/retrieval/_collections/fam-rule-scope-context.util.js +74 -0
  63. package/build/src/_modules/retrieval/_services/fam-global-rule.control-service.js +82 -0
  64. package/build/src/_modules/retrieval/_services/fam-memory-cold-search.control-service.js +121 -0
  65. package/build/src/_modules/retrieval/_services/fam-memory-dormancy.control-service.js +99 -0
  66. package/build/src/_modules/retrieval/_services/fam-memory-reactivation.control-service.js +68 -0
  67. package/build/src/_modules/retrieval/_services/fam-retrieval-candidate.data-service.js +15 -2
  68. package/build/src/_modules/retrieval/_services/fam-retrieval.control-service.js +197 -5
  69. package/build/src/_modules/retrieval/_services/fam-rule-injection.control-service.js +102 -0
  70. package/build/src/_modules/retrieval/index.js +12 -1
  71. package/build/src/_modules/scope-reference/_collections/fam-scope-maintenance.util.js +134 -0
  72. package/build/src/_modules/scope-reference/_services/fam-scope-maintenance.control-service.js +423 -0
  73. package/build/src/_modules/scope-reference/_services/fam-scope-resolver.control-service.js +2 -2
  74. package/build/src/_modules/scope-reference/_services/fam-scope.data-service.js +25 -0
  75. package/build/src/_modules/scope-reference/index.js +4 -1
  76. package/build/src/_routes/server/api/api.controller.js +324 -23
  77. package/build/src/_routes/server/api/fam-doctor.control-service.js +156 -0
  78. package/build/src/_routes/server/config/config.control-service.js +2 -2
  79. package/build/src/app.server.js +29 -27
  80. package/client-dist/{chunk-YXHWCJ5O.js → chunk-GF3FJP7E.js} +71 -71
  81. package/client-dist/chunk-NMUCMQY6.js +1 -0
  82. package/client-dist/favicon.ico +0 -0
  83. package/client-dist/index.html +3 -3
  84. package/client-dist/main-YK4YIVAQ.js +2 -0
  85. package/package.json +2 -2
  86. package/build/src/_cli/_collections/fam-project-discovery.util.js +0 -98
  87. package/build/src/_collections/fam-heap-guard.control-service.js +0 -176
  88. package/client-dist/chunk-I77GXVAQ.js +0 -1
  89. package/client-dist/main-PJPEDVJT.js +0 -2
@@ -1,16 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Api_Controller = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const mongoose_1 = tslib_1.__importDefault(require("mongoose"));
4
6
  const fsm_dynamo_1 = require("@futdevpro/fsm-dynamo");
5
7
  const nts_dynamo_1 = require("@futdevpro/nts-dynamo");
8
+ const nts_dynamo_2 = require("@futdevpro/nts-dynamo");
6
9
  const fam_version_const_1 = require("../../../_collections/fam-version.const");
7
- const fam_heap_guard_control_service_1 = require("../../../_collections/fam-heap-guard.control-service");
8
10
  const fam_table_type_enum_1 = require("../../../_enums/fam-table.type-enum");
9
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");
10
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");
11
18
  const embedding_1 = require("../../../_modules/embedding");
12
19
  const ingest_1 = require("../../../_modules/ingest");
13
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");
14
24
  /** A UI/CLI lapozás CCAP-örökség konstansai (dsgn-010/dsgn-011 §4.1). */
15
25
  const DEFAULT_PAGE_SIZE = 50;
16
26
  const MAX_PAGE_SIZE = 500;
@@ -51,6 +61,9 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
51
61
  this.writeEndpoint(),
52
62
  this.capabilitiesEndpoint(),
53
63
  this.healthEndpoint(),
64
+ this.doctorEndpoint(),
65
+ this.rulesInjectEndpoint(),
66
+ this.rulesPropagateEndpoint(),
54
67
  this.itemsEndpoint(),
55
68
  this.statsEndpoint(),
56
69
  this.statsTableEndpoint(),
@@ -58,6 +71,12 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
58
71
  this.duplicatesEndpoint(),
59
72
  this.configGetEndpoint(),
60
73
  this.configSetEndpoint(),
74
+ this.scanStartEndpoint(),
75
+ this.scanStatusEndpoint(),
76
+ this.scanJobsEndpoint(),
77
+ this.scanCancelEndpoint(),
78
+ this.errorsSummaryEndpoint(),
79
+ this.errorsEndpoint(),
61
80
  ];
62
81
  }
63
82
  // =========================================================================
@@ -108,6 +127,14 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
108
127
  // Heap-load-shed (OOM-hibakezelés): a NEHÉZ write-eket (scan/import/re-embed) critical
109
128
  // memória-nyomásnál elutasítjuk (503), MIELŐTT egy nagy allokáció átlökné a plafont.
110
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
+ }
111
138
  return mcp_1.FAM_WriteTool_Service.getInstance().handle(input);
112
139
  });
113
140
  },
@@ -130,9 +157,235 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
130
157
  ],
131
158
  });
132
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
+ }
133
323
  // =========================================================================
134
324
  // /health — CLI remote reachability-check (dsgn-010 §9)
135
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
+ }
136
389
  /** `GET /health` — a CLI `--server-url` elérhetőség-próbája (dsgn-010 §9). */
137
390
  healthEndpoint() {
138
391
  return new nts_dynamo_1.DyNTS_Endpoint_Params({
@@ -141,12 +394,19 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
141
394
  endpoint: '/health',
142
395
  tasks: [
143
396
  async (req, res) => {
144
- // A heap-nyomás IS kimegy a health-en (observability — a CLI/UI/agent látja a memória-állapotot).
145
- const memory = fam_heap_guard_control_service_1.FAM_HeapGuard_ControlService.getInstance().pressure();
397
+ // A heap-nyomás IS kimegy a health-en (observability) — a BEDROCK DyNTS_MemoryGuard pressure-éből.
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();
146
403
  res.send({
147
404
  ok: true, service: 'fdp-agent-memory', version: fam_version_const_1.FAM_VERSION, uptime: process.uptime(),
148
- memory: { level: memory.level, usedMb: memory.usedMb, limitMb: memory.limitMb,
149
- ratio: Math.round(memory.ratio * 100) / 100, gcFraction: Math.round(memory.gcFraction * 100) / 100 },
405
+ ready: hydration.complete,
406
+ memory: { level: memory.level, heapUsedMb: memory.heapUsedMb, heapLimitMb: memory.heapLimitMb,
407
+ heapPct: memory.heapPct, rssMb: memory.rssMb, gcFraction: memory.gcFraction },
408
+ hydration: hydration,
409
+ readiness: readiness,
150
410
  });
151
411
  },
152
412
  ],
@@ -193,13 +453,27 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
193
453
  if (req.query.ingestRunId) {
194
454
  filter.ingestRunId = String(req.query.ingestRunId);
195
455
  }
196
- const dataService = new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
197
- const all = await dataService.findHydratableList(filter);
198
456
  const pageSize = Math.min(this.parseNumber(req.query.pageSize, DEFAULT_PAGE_SIZE), MAX_PAGE_SIZE);
199
457
  const page = Math.max(this.parseNumber(req.query.page, 1), 1);
200
458
  const start = (page - 1) * pageSize;
201
- const items = all.slice(start, start + pageSize);
202
- return { table: table, total: all.length, page: page, pageSize: pageSize, items: items };
459
+ // FIX (OOM): DB-OLDALI lapozás + `contentVector` projekció-kizárás. KORÁBBAN `findHydratableList({})` a TELJES
460
+ // tárat (126k+ teljes doc + 2560-dim vektor) memóriába töltötte ÉS memóriában szeletelt → a UI-polling többször
461
+ // GB-okat allokált → OOM + Mongo-flapping. Most: `countDocuments` + `skip/limit` + a nagy vektor-mező kihagyva.
462
+ const collection = this.entryCollection(registryEntry);
463
+ const total = await collection.countDocuments(filter);
464
+ const items = await collection
465
+ .find(filter, { projection: { contentVector: 0 } })
466
+ .sort({ __lastModified: -1 })
467
+ .skip(start)
468
+ .limit(pageSize)
469
+ .toArray();
470
+ return { table: table, total: total, page: page, pageSize: pageSize, items: items };
471
+ }
472
+ /** Egy fő RAG-tár mongoose-collection-je (a DS-példányosítás regisztrálja a modellt; onnan a valós collection). */
473
+ entryCollection(registryEntry) {
474
+ new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
475
+ const model = mongoose_1.default.models[registryEntry.dataParams.dataName];
476
+ return model.collection;
203
477
  }
204
478
  // =========================================================================
205
479
  // /stats · /stats/:table — high-level stats aggregátum (dsgn-011 §3/§4.4)
@@ -234,7 +508,11 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
234
508
  ],
235
509
  });
236
510
  }
237
- /** A tár-stats aggregátum (aktív elem-szám + embedding-státusz + utolsó ingest) — dsgn-011 §3/§4.4. */
511
+ /**
512
+ * A tár-stats aggregátum (aktív elem-szám + embedding-státusz + utolsó ingest) — dsgn-011 §3/§4.4.
513
+ * FIX (OOM): `countDocuments` + `$group`-aggregáció + 1-doc last-modified projekció — KORÁBBAN `findHydratableList({})`
514
+ * a TELJES tárat memóriába töltötte (a UI stats-pollingja → ismételt GB-allokáció → OOM). Most semmit nem hidratál.
515
+ */
238
516
  async collectTableStats(tables) {
239
517
  const result = [];
240
518
  for (const table of tables) {
@@ -242,24 +520,35 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
242
520
  if (!registryEntry) {
243
521
  continue;
244
522
  }
245
- const dataService = new embedding_1.FAM_Entry_DataService({ dataParams: registryEntry.dataParams, issuer: this.issuer });
246
- const entries = await dataService.findHydratableList({});
523
+ const collection = this.entryCollection(registryEntry);
524
+ const count = await collection.countDocuments({});
525
+ // Embedding-státusz bontás Mongo-aggregációval ($group), NEM a doc-ok memóriába töltésével.
247
526
  const byStatus = { pending: 0, completed: 0, error: 0 };
248
- let lastModifiedMs = 0;
249
- for (const entry of entries) {
250
- const status = entry.embeddingStatus ?? 'pending';
251
- byStatus[status] = (byStatus[status] ?? 0) + 1;
252
- const modified = entry.__lastModified ? new Date(entry.__lastModified).getTime() : 0;
253
- if (modified > lastModifiedMs) {
254
- lastModifiedMs = modified;
527
+ const grouped = await collection
528
+ .aggregate([{ $group: { _id: '$embeddingStatus', n: { $sum: 1 } } }])
529
+ .toArray();
530
+ for (const group of grouped) {
531
+ const status = group._id ?? 'pending';
532
+ if (status in byStatus) {
533
+ byStatus[status] += group.n;
534
+ }
535
+ else {
536
+ byStatus.pending += group.n;
255
537
  }
256
538
  }
539
+ // Utolsó módosítás: EGYETLEN doc (a legfrissebb), csak az időbélyeg-mezőre projektálva.
540
+ const newest = await collection
541
+ .find({}, { projection: { __lastModified: 1 } })
542
+ .sort({ __lastModified: -1 })
543
+ .limit(1)
544
+ .toArray();
545
+ const lastModifiedMs = newest[0]?.__lastModified ? new Date(newest[0].__lastModified).getTime() : 0;
257
546
  result.push({
258
547
  table: table,
259
- count: entries.length,
548
+ count: count,
260
549
  embeddingStatus: byStatus,
261
550
  lastModified: lastModifiedMs ? new Date(lastModifiedMs).toISOString() : null,
262
- status: entries.length ? 'ok' : 'empty',
551
+ status: count ? 'ok' : 'empty',
263
552
  });
264
553
  }
265
554
  return result;
@@ -313,12 +602,24 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
313
602
  async (req, res) => {
314
603
  await this.run(res, async () => {
315
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);
316
609
  return embedding_1.FAM_DuplicateScan_ControlService.getInstance().scan({
317
610
  table: table,
318
611
  threshold: req.query.threshold !== undefined ? Number(req.query.threshold) : undefined,
319
612
  neighbors: req.query.neighbors !== undefined ? this.parseNumber(req.query.neighbors, 10) : undefined,
320
613
  maxEntries: req.query.maxEntries !== undefined ? this.parseNumber(req.query.maxEntries, 3000) : undefined,
321
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,
322
623
  });
323
624
  });
324
625
  },
@@ -434,14 +735,14 @@ class Api_Controller extends nts_dynamo_1.DyNTS_Controller {
434
735
  if (!Api_Controller.HEAVY_WRITE_OPERATIONS.includes(operation)) {
435
736
  return;
436
737
  }
437
- const guard = fam_heap_guard_control_service_1.FAM_HeapGuard_ControlService.getInstance();
738
+ const guard = nts_dynamo_2.DyNTS_MemoryGuard.getInstance();
438
739
  if (!guard.shouldShedLoad()) {
439
740
  return;
440
741
  }
441
742
  const pressure = guard.pressure();
442
743
  throw new fsm_dynamo_1.DyFM_Error({
443
744
  errorCode: 'FAM-MEM-SHED-001',
444
- message: `Memória-nyomás (${pressure.usedMb}/${pressure.limitMb} MB, ${Math.round(pressure.ratio * 100)}%, `
745
+ message: `Memória-nyomás (${pressure.heapUsedMb}/${pressure.heapLimitMb} MB, ${pressure.heapPct}%, `
445
746
  + `GC ${Math.round(pressure.gcFraction * 100)}%) — a nehéz '${operation}' művelet ideiglenesen elutasítva `
446
747
  + '(load-shed). Próbáld újra, amikor a nyomás enyhül (lásd /api/logs), vagy szakaszold a scant (cooldown).',
447
748
  issuer: this.issuer,
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAM_Doctor_ControlService = void 0;
4
+ const mcp_1 = require("../../../_modules/mcp");
5
+ const scope_reference_1 = require("../../../_modules/scope-reference");
6
+ /** A 6 doctor al-check ↔ capability-eszköz mátrix (dsgn-010 §5.2; a `fam doctor` CLI-mátrixával egyező). */
7
+ const CHECK_MATRIX = [
8
+ { name: 'env', capability: 'validate_environment', fixHint: 'Hiányzó env (pl. MONGODB_URI) → `fam init` vagy állítsd be a `.env`-ben.' },
9
+ { name: 'mongodb', capability: 'test_mongodb_connection', fixHint: 'Mongo nem elérhető → indítsd a Mongo-t, ellenőrizd a portot / MONGODB_URI-t.' },
10
+ { name: 'config', capability: 'get_config_status', fixHint: 'Nincs fam_config global rekord → `fam init` vagy `fam config set …`.' },
11
+ { name: 'embedding', capability: 'test_embedding_provider', fixHint: 'OpenAI 401 → ellenőrizd OPENAI_API_KEY-t; LM Studio: indítsd a szervert + tölts be modellt.' },
12
+ { name: 'vector', capability: 'test_vector_search', fixHint: 'Vektor-pool üres/nem-hidratált → `fam write … re-embed` vagy ellenőrizd a Mongo-persist réteget.' },
13
+ { name: 'collections', capability: 'get_table_stats', fixHint: 'Hiányzó collection → indítsd a szervert egyszer (auto-create), majd `fam doctor`.' },
14
+ ];
15
+ /**
16
+ * `FAM_Doctor_ControlService` (#2 diagnosztikai végpont, user-FR 2026-06-24) — a `fam doctor` aggregátorának
17
+ * **SZERVER-OLDALI** változata (server-centric / thin-CLI, ADR-015). A 6 capability-diagnosztikát IN-PROCESS futtatja
18
+ * (`FAM_CapabilitiesTool_Service.handle`, nem HTTP-round-trip), és aggregált health-report-ot ad (+ best-effort
19
+ * scope-integritás). A `/api/doctor` ezt szolgálja ki; a CLI/kliens/UI/külső eszköz ezt hívhatja (nem 6 külön
20
+ * capability-hívást). Az `interpret` PURE (a capability szemantikus hiba-jelzéseit — `available/reachable/ok:false`,
21
+ * `missing[]` — fail-ként ismeri fel, nem csak a throw-t; a CLI csak a throw-t fogta).
22
+ */
23
+ class FAM_Doctor_ControlService {
24
+ static _instance;
25
+ static getInstance() {
26
+ if (!FAM_Doctor_ControlService._instance) {
27
+ FAM_Doctor_ControlService._instance = new FAM_Doctor_ControlService();
28
+ }
29
+ return FAM_Doctor_ControlService._instance;
30
+ }
31
+ /**
32
+ * A capability-eredmény SZEMANTIKUS hiba-jelzése (PURE, tesztelhető): a technikailag sikeres hívás is jelezhet
33
+ * hibát (`available:false` = embedding down; `reachable:false` = Mongo down; `ok:false`; `missing[]` = hiányzó env).
34
+ * A wrapper-réteget (`data`/`result`) is megnézi. `true` = egészségtelen (→ FAIL).
35
+ */
36
+ static interpretUnhealthy(result) {
37
+ const root = (result && typeof result === 'object') ? result : {};
38
+ const inner = root.data ?? root.result ?? root;
39
+ const flat = (inner && typeof inner === 'object') ? inner : root;
40
+ return flat.available === false
41
+ || flat.reachable === false
42
+ || flat.ok === false
43
+ || (Array.isArray(flat.missing) && flat.missing.length > 0);
44
+ }
45
+ /** A teljes (vagy `skip`-elt) check-szett lefuttatása + aggregált health-report. */
46
+ async runDoctor(set) {
47
+ const skip = set?.skip ?? [];
48
+ const checks = [];
49
+ for (const descriptor of CHECK_MATRIX) {
50
+ if (skip.includes(descriptor.name)) {
51
+ continue;
52
+ }
53
+ checks.push(await this.runCheck(descriptor));
54
+ }
55
+ const summary = { ok: 0, warn: 0, fail: 0 };
56
+ for (const check of checks) {
57
+ summary[check.status] += 1;
58
+ }
59
+ const health = summary.fail ? 'fail' : summary.warn ? 'warn' : 'ok';
60
+ return {
61
+ health: health,
62
+ checks: checks,
63
+ summary: summary,
64
+ scopeIntegrity: await this.scopeIntegrity(),
65
+ database: await this.databaseInfo(),
66
+ };
67
+ }
68
+ /**
69
+ * Best-effort DB-diagnosztika (user-FR 2026-06-24): a 6 fő tár elem-számai + embedding-státusza (get_table_stats)
70
+ * + a helper-collection-ök mérete + az összes aktív doc (get_storage_usage). A hiba NEM buktatja a report-ot.
71
+ */
72
+ async databaseInfo() {
73
+ try {
74
+ const statsRaw = await mcp_1.FAM_CapabilitiesTool_Service.getInstance().handle({ action: 'invoke', name: 'get_table_stats' });
75
+ const storageRaw = await mcp_1.FAM_CapabilitiesTool_Service.getInstance().handle({ action: 'invoke', name: 'get_storage_usage' });
76
+ const statsList = FAM_Doctor_ControlService.unwrapArray(statsRaw);
77
+ const storage = FAM_Doctor_ControlService.unwrapRecord(storageRaw);
78
+ const tables = (Array.isArray(statsList) ? statsList : []).map((raw) => {
79
+ const item = (raw && typeof raw === 'object') ? raw : {};
80
+ const es = (item.embeddingStatus && typeof item.embeddingStatus === 'object')
81
+ ? item.embeddingStatus : {};
82
+ return {
83
+ table: String(item.table ?? ''),
84
+ count: Number(item.count) || 0,
85
+ completed: Number(es.completed) || 0,
86
+ pending: Number(es.pending) || 0,
87
+ error: Number(es.error) || 0,
88
+ };
89
+ });
90
+ const mainCollections = /^fam_(rules|documents|codebases|knowledges|coding_patterns|memories)$/;
91
+ const collectionsRaw = storage.collections;
92
+ const helperCollections = (Array.isArray(collectionsRaw) ? collectionsRaw : [])
93
+ .map((raw) => {
94
+ const item = (raw && typeof raw === 'object') ? raw : {};
95
+ return { collection: String(item.collection ?? ''), activeDocs: Number(item.activeDocs) || 0 };
96
+ })
97
+ .filter((entry) => entry.collection && !mainCollections.test(entry.collection));
98
+ return {
99
+ tables: tables,
100
+ totalActiveDocs: typeof storage.totalActiveDocs === 'number' ? storage.totalActiveDocs : undefined,
101
+ helperCollections: helperCollections,
102
+ };
103
+ }
104
+ catch {
105
+ return undefined;
106
+ }
107
+ }
108
+ /** A capability-eredmény tömb-payloadja (a `this.ok` wrapper-réteget is megnézve). */
109
+ static unwrapArray(result) {
110
+ if (Array.isArray(result)) {
111
+ return result;
112
+ }
113
+ const root = (result && typeof result === 'object') ? result : {};
114
+ const inner = root.data ?? root.result ?? root;
115
+ return Array.isArray(inner) ? inner : [];
116
+ }
117
+ /** A capability-eredmény objektum-payloadja (a `this.ok` wrapper-réteget is megnézve). */
118
+ static unwrapRecord(result) {
119
+ const root = (result && typeof result === 'object') ? result : {};
120
+ const inner = root.data ?? root.result ?? root;
121
+ return (inner && typeof inner === 'object') ? inner : root;
122
+ }
123
+ /** Egy capability-check IN-PROCESS futtatása → `FAM_DoctorCheck` (throw → FAIL/WARN; szemantikus hiba → FAIL). */
124
+ async runCheck(descriptor) {
125
+ try {
126
+ const result = await mcp_1.FAM_CapabilitiesTool_Service.getInstance()
127
+ .handle({ action: 'invoke', name: descriptor.capability });
128
+ if (FAM_Doctor_ControlService.interpretUnhealthy(result)) {
129
+ return { name: descriptor.name, status: 'fail', detail: JSON.stringify(result).slice(0, 240), fixHint: descriptor.fixHint };
130
+ }
131
+ return { name: descriptor.name, status: 'ok' };
132
+ }
133
+ catch (error) {
134
+ const message = error?.message ?? 'ismeretlen hiba';
135
+ // A late-bind eszközök clear "pending MP-X" hibája → WARN (nem FAIL, nem néma).
136
+ const isPending = message.toLowerCase().includes('pending');
137
+ return { name: descriptor.name, status: isPending ? 'warn' : 'fail', detail: message, fixHint: descriptor.fixHint };
138
+ }
139
+ }
140
+ /** Best-effort scope-integritás-összegzés (a detect_scope_issues hibája NEM buktatja a doctor-report-ot). */
141
+ async scopeIntegrity() {
142
+ try {
143
+ const issues = await scope_reference_1.FAM_ScopeMaintenance_ControlService.getInstance().detectIssues();
144
+ return {
145
+ hasIssues: issues.hasIssues,
146
+ orphanScopes: issues.orphanProjectScopes.length,
147
+ duplicateScopes: issues.duplicateScopes.length,
148
+ repoKeyDuplicates: issues.repoKeyDuplicates.length,
149
+ };
150
+ }
151
+ catch {
152
+ return undefined;
153
+ }
154
+ }
155
+ }
156
+ exports.FAM_Doctor_ControlService = FAM_Doctor_ControlService;
@@ -163,7 +163,7 @@ class FAM_Config_ControlService {
163
163
  const dataService = new config_data_service_1.FAM_Config_DataService({ issuer: options.issuer ?? this.issuer });
164
164
  // Régi aktív rekord (ha van) → soft-delete (archív; history-megőrzés, dsgn-007 §7.2).
165
165
  const existing = await dataService.findActive(filter);
166
- if (existing && existing._id) {
166
+ if (existing?._id) {
167
167
  await dataService.deleteData(existing._id);
168
168
  }
169
169
  // Új aktív rekord — a `value` Mixed; a saveData $set-tel ír (Mongoose Mixed silent-drop ellen).
@@ -435,7 +435,7 @@ class FAM_Config_ControlService {
435
435
  async seedGlobalIfMissing(key, value) {
436
436
  const dataService = new config_data_service_1.FAM_Config_DataService({ issuer: this.issuer });
437
437
  const existing = await dataService.findActive({ level: fam_config_level_type_enum_1.FAM_ConfigLevel.global, key: key });
438
- if (existing && existing._id) {
438
+ if (existing?._id) {
439
439
  return;
440
440
  }
441
441
  await this.set(key, value, { level: fam_config_level_type_enum_1.FAM_ConfigLevel.global, setBy: 'env-seed', issuer: this.issuer });