@futdevpro/fdp-agent-memory 1.1.119 → 1.1.130

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@futdevpro/fdp-agent-memory",
3
- "version": "1.1.119",
3
+ "version": "1.1.130",
4
4
  "description": "Local-first, vector-backed multi-table agent memory exposed as an MCP server (read/write/capabilities). Public, FDP-Templates-free, no auth.",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -89,18 +89,41 @@ class FAM_Read_Util {
89
89
  humanFormatter: (data) => FAM_Read_Util.formatHuman(data),
90
90
  });
91
91
  }
92
- /** Ember-olvasható read-kimenet: query-nként a hits + a dense-jelzés kötelező kiemelése (REQ-007). */
92
+ /**
93
+ * Ember-olvasható read-kimenet: query-nként a `summary` (operatív visszajelzés) + a találatok ÚTVONALLAL, majd a
94
+ * konkrét következő lépések (`hints`). 2026-07-29 bővítés: eddig csak `[score] id + előnézet` volt — sem az
95
+ * ÚTVONAL (a „nem állnak össze a fájl-útvonalak" panasz), sem a „mennyiből mennyit látok" nem látszott.
96
+ */
93
97
  static formatHuman(data) {
94
98
  const results = data.results ?? [];
95
99
  const lines = [];
96
100
  for (const block of results) {
97
- lines.push(`\n● "${block.query ?? ''}" [${(block.tables ?? []).join(', ')}] (${block.totalRelevant ?? block.hits?.length ?? 0} relevant)`);
101
+ lines.push(`\n● "${block.query ?? ''}" [${(block.tables ?? []).join(', ')}]`);
102
+ if (block.summary) {
103
+ lines.push(` ${block.summary}`);
104
+ }
105
+ else {
106
+ lines.push(` (${block.totalRelevant ?? block.hits?.length ?? 0} relevant)`);
107
+ }
98
108
  if (block.denseResults) {
99
109
  lines.push(` ⚑ DENSE RESULTS — szűkítsd: ${(block.suggestions ?? []).join(' | ') || '(adj scope-ot/tag-et)'}`);
100
110
  }
101
111
  for (const hit of block.hits ?? []) {
102
112
  const preview = (hit.content ?? '').replace(/\s+/g, ' ').slice(0, 100);
103
- lines.push(` - [${(hit.score ?? 0).toFixed(3)}] ${hit.id ?? ''} ${preview}`);
113
+ // A MEGNYITHATÓ útvonal + a fájlon belüli hely — enélkül a találatból nem lehetett visszatalálni a forráshoz.
114
+ const location = hit.path
115
+ ? `${hit.path}${hit.lines ? `:${hit.lines}` : ''}`
116
+ : (hit.id ?? '');
117
+ lines.push(` - [${(hit.score ?? 0).toFixed(3)}] ${location}`);
118
+ if (hit.heading) {
119
+ lines.push(` ${hit.heading}`);
120
+ }
121
+ if (preview) {
122
+ lines.push(` ${preview}`);
123
+ }
124
+ }
125
+ for (const hint of block.hints ?? []) {
126
+ lines.push(` → ${hint}`);
104
127
  }
105
128
  }
106
129
  return lines.join('\n');
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAM_Remote_Util = void 0;
4
+ const commander_1 = require("commander");
5
+ const fam_remote_config_util_1 = require("../../_collections/fam-remote-config.util");
6
+ const fam_output_util_1 = require("../_collections/fam-output.util");
7
+ const fam_cli_const_1 = require("../_collections/fam-cli.const");
8
+ const fam_client_service_1 = require("../_services/fam-client.service");
9
+ /**
10
+ * `fam remote show|set|clear` — a TÁVOLI ELÉRÉS beállítása (user-FR 2026-07-28: „kéne építsünk a FAM-hoz beállítást,
11
+ * hogy távoli elérésként is működhessen — pl. virtuális gépről a host-ra kapcsolva, hogy a host futtat, de a VM is
12
+ * eléri, más címen/útvonalon").
13
+ *
14
+ * A beállítás a gép-lokális `~/.fam/config.json`-ben él (NEM a DB-config — az a szerveren van, amihez épp csatlakozni
15
+ * akarunk; és NEM új env-var — a hookok/MCP/CLI külön process-ek, egy fájl mindnek szól).
16
+ *
17
+ * **Tipikus VM→host setup:**
18
+ * - HOST-on: `fam remote set --bind 192.168.56.1` (a host-only adapter IP-je) + `fam serve --restart`
19
+ * - VM-ben: `fam remote set --url http://192.168.56.1:39265` → a VM CLI/hook/MCP a hostot használja
20
+ * - Reverse proxy mögött: `fam remote set --url https://host.local/fam` (az útvonal-prefix támogatott)
21
+ */
22
+ class FAM_Remote_Util {
23
+ /** A `remote` parancs-deszkriptor (show/set/clear alparanccsal). */
24
+ static command() {
25
+ const command = new commander_1.Command('remote');
26
+ command.description('Távoli elérés beállítása (~/.fam/config.json): kliens szerver-URL + szerver bind-host');
27
+ command.addCommand(FAM_Remote_Util.showCommand());
28
+ command.addCommand(FAM_Remote_Util.setCommand());
29
+ command.addCommand(FAM_Remote_Util.clearCommand());
30
+ return command;
31
+ }
32
+ /** `remote show` — az aktuális (feloldott) beállítás + a tényleges elérhetőség. */
33
+ static showCommand() {
34
+ const command = new commander_1.Command('show');
35
+ command
36
+ .description('A jelenlegi távoli-elérés beállítás + elérhetőség-próba')
37
+ .action(async () => {
38
+ fam_output_util_1.FAM_Output_Util.exit(await FAM_Remote_Util.runShow(command));
39
+ });
40
+ return command;
41
+ }
42
+ /** `remote set` — a szerver-URL és/vagy a bind-host beállítása. */
43
+ static setCommand() {
44
+ const command = new commander_1.Command('set');
45
+ command
46
+ .description('A kliens szerver-URL és/vagy a szerver bind-host beállítása')
47
+ .option('--url <url>', 'KLIENS: a FAM szerver base-URL-je (útvonal-prefix is lehet, pl. https://host/fam)')
48
+ .option('--bind <host>', 'SZERVER: bind-host (127.0.0.1 = csak lokális, 0.0.0.0 vagy "*" = minden interfész, '
49
+ + 'vagy egy konkrét adapter-IP). Csak a szerver ÚJRAINDÍTÁSA után lép életbe.')
50
+ .action(async () => {
51
+ fam_output_util_1.FAM_Output_Util.exit(await FAM_Remote_Util.runSet(command));
52
+ });
53
+ return command;
54
+ }
55
+ /** `remote clear` — a beállítások törlése (vissza a lokális default-ra). */
56
+ static clearCommand() {
57
+ const command = new commander_1.Command('clear');
58
+ command
59
+ .description('A távoli-elérés beállítások törlése (vissza a lokális default-ra)')
60
+ .action(async () => {
61
+ fam_output_util_1.FAM_Output_Util.exit(await FAM_Remote_Util.runClear(command));
62
+ });
63
+ return command;
64
+ }
65
+ /** A `remote show` futtatása: a feloldott értékek + honnan jönnek + elérhető-e a szerver. */
66
+ static async runShow(command) {
67
+ const globals = command.optsWithGlobals();
68
+ const file = fam_remote_config_util_1.FAM_RemoteConfig_Util.load();
69
+ const serverUrl = fam_remote_config_util_1.FAM_RemoteConfig_Util.resolveServerUrl({
70
+ override: globals.serverUrl, env: process.env.FAM_SERVER_URL, file: file,
71
+ });
72
+ const bindHost = fam_remote_config_util_1.FAM_RemoteConfig_Util.resolveBindHost({ env: process.env.FAM_SERVER_HOST, file: file });
73
+ const client = new fam_client_service_1.FAM_CliClient_Service(globals.serverUrl);
74
+ return fam_output_util_1.FAM_Output_Util.success({
75
+ options: globals,
76
+ command: 'remote show',
77
+ data: {
78
+ configPath: fam_remote_config_util_1.FAM_RemoteConfig_Util.CONFIG_PATH,
79
+ serverUrl: serverUrl,
80
+ serverUrlFrom: FAM_Remote_Util.describeSource(globals.serverUrl, process.env.FAM_SERVER_URL, file.serverUrl),
81
+ bindHost: bindHost,
82
+ bindHostFrom: FAM_Remote_Util.describeSource(undefined, process.env.FAM_SERVER_HOST, file.bindHost),
83
+ bindIsLoopbackOnly: fam_remote_config_util_1.FAM_RemoteConfig_Util.isLoopbackHost(bindHost),
84
+ reachable: await client.isReachable(),
85
+ },
86
+ });
87
+ }
88
+ /** A `remote set` futtatása. */
89
+ static async runSet(command) {
90
+ const globals = command.optsWithGlobals();
91
+ const local = command.opts();
92
+ if (!local.url && !local.bind) {
93
+ return fam_output_util_1.FAM_Output_Util.failure({
94
+ options: globals, command: 'remote set',
95
+ error: {
96
+ errorCode: 'FAM-CLI-REMOTE-001',
97
+ message: 'Adj meg legalább egyet: `--url` (kliens) vagy `--bind` (szerver).',
98
+ fixHint: 'Pl. VM-ből a hostra: `fam remote set --url http://192.168.56.1:39265`; '
99
+ + 'a hoston: `fam remote set --bind 192.168.56.1` + `fam serve --restart`.',
100
+ },
101
+ exitCode: fam_cli_const_1.FAM_CliExitCode.usageError,
102
+ });
103
+ }
104
+ const saved = fam_remote_config_util_1.FAM_RemoteConfig_Util.save({ serverUrl: local.url, bindHost: local.bind });
105
+ const notes = [];
106
+ if (local.bind) {
107
+ notes.push('A bind-host CSAK a szerver újraindítása után lép életbe: `fam serve --restart`.');
108
+ const effective = fam_remote_config_util_1.FAM_RemoteConfig_Util.resolveBindHost({ file: saved });
109
+ if (!fam_remote_config_util_1.FAM_RemoteConfig_Util.isLoopbackHost(effective)) {
110
+ notes.push('FIGYELEM: a FAM-nak NINCS authentikációja (ADR-004) — a nem-loopback bind a TELJES '
111
+ + 'korpuszt elérhetővé teszi az adott hálózaton. Csak megbízható (host-only / VPN) hálón használd.');
112
+ }
113
+ }
114
+ return fam_output_util_1.FAM_Output_Util.success({
115
+ options: globals, command: 'remote set',
116
+ data: { configPath: fam_remote_config_util_1.FAM_RemoteConfig_Util.CONFIG_PATH, config: saved, notes: notes },
117
+ });
118
+ }
119
+ /** A `remote clear` futtatása. */
120
+ static async runClear(command) {
121
+ const globals = command.optsWithGlobals();
122
+ const saved = fam_remote_config_util_1.FAM_RemoteConfig_Util.save({ serverUrl: null, bindHost: null });
123
+ return fam_output_util_1.FAM_Output_Util.success({
124
+ options: globals, command: 'remote clear',
125
+ data: { configPath: fam_remote_config_util_1.FAM_RemoteConfig_Util.CONFIG_PATH, config: saved,
126
+ notes: ['Vissza a lokális default-ra (kliens: http://127.0.0.1:39265, bind: 127.0.0.1). '
127
+ + 'A bind változás a szerver újraindításakor lép életbe.'] },
128
+ });
129
+ }
130
+ /** Melyik forrásból jött a feloldott érték (flag > env > fájl > default) — a `show` átláthatóságához (PURE). */
131
+ static describeSource(flag, env, file) {
132
+ if (flag?.trim()) {
133
+ return 'flag (--server-url)';
134
+ }
135
+ if (env?.trim()) {
136
+ return 'env';
137
+ }
138
+ return file?.trim() ? 'config-fájl (~/.fam/config.json)' : 'beépített default';
139
+ }
140
+ }
141
+ exports.FAM_Remote_Util = FAM_Remote_Util;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FAM_CliClient_Service = void 0;
4
- const fam_cli_const_1 = require("../_collections/fam-cli.const");
4
+ const fam_remote_config_util_1 = require("../../_collections/fam-remote-config.util");
5
5
  /**
6
6
  * `FAM_CliClient_Service` — a CLI → szerver elérési réteg (dsgn-010 §9). A `read`/`write`/`scan`/
7
7
  * `stats`/`errors`/`config`/`capabilities` parancsok ezen át hívják a futó szerver REST-felületét
@@ -25,8 +25,12 @@ class FAM_CliClient_Service {
25
25
  * `FAM_SERVER_URL`, különben a `FAM_DEFAULT_SERVER_URL`.
26
26
  */
27
27
  constructor(serverUrlOverride) {
28
- const raw = serverUrlOverride || process.env.FAM_SERVER_URL || fam_cli_const_1.FAM_DEFAULT_SERVER_URL;
29
- this.baseUrl = raw.replace(/\/+$/, '');
28
+ // TÁVOLI ELÉRÉS (user-FR 2026-07-28): a `~/.fam/config.json` `serverUrl`-je is beleszól — így egy VM-ből a
29
+ // host-on futó FAM elérhető anélkül, hogy MINDEN hívóhoz (CLI/hook/MCP külön process!) env-et kellene fűzni.
30
+ // Precedencia: `--server-url` flag > `FAM_SERVER_URL` env > config-fájl > `http://127.0.0.1:39265`.
31
+ this.baseUrl = fam_remote_config_util_1.FAM_RemoteConfig_Util.resolveServerUrl({
32
+ override: serverUrlOverride, env: process.env.FAM_SERVER_URL, file: fam_remote_config_util_1.FAM_RemoteConfig_Util.load(),
33
+ });
30
34
  }
31
35
  /** A feloldott szerver base-URL (a fix-tippekhez / diaghoz). */
32
36
  getBaseUrl() {
@@ -17,6 +17,7 @@ const seed_command_1 = require("./_commands/seed.command");
17
17
  const import_command_1 = require("./_commands/import.command");
18
18
  const export_command_1 = require("./_commands/export.command");
19
19
  const capture_command_1 = require("./_commands/capture.command");
20
+ const remote_command_1 = require("./_commands/remote.command");
20
21
  /**
21
22
  * Regisztrálja az összes FDP Agent Memory-specifikus CLI command-ot (dsgn-010 §2 `buildCli()`).
22
23
  *
@@ -52,6 +53,7 @@ function registerFAMCommands(program, version) {
52
53
  export_command_1.FAM_Export_Util.command(),
53
54
  capture_command_1.FAM_Capture_Util.command(),
54
55
  config_command_1.FAM_Config_Util.command(),
56
+ remote_command_1.FAM_Remote_Util.command(),
55
57
  stats_command_1.FAM_Stats_Util.command(),
56
58
  find_duplicates_command_1.FAM_FindDuplicates_Util.command(),
57
59
  errors_command_1.FAM_Errors_Util.command(),
@@ -71,12 +71,36 @@ exports.CONFIG_CATALOG = {
71
71
  // --- 4.1. Read / retrieval limitek (fogyasztó: dsgn-005) ---
72
72
  'read.topK': {
73
73
  type: 'number', default: 5, min: 1, max: 200, integer: true, levels: ALL_LEVELS,
74
- description: 'Alap visszaadott találat-szám (CCAP DEFAULT_TOP_K=5).',
74
+ // PER-TÁR default (user-FR 2026-07-28): „amikor rule-okat vagy más FONTOS dolgot keresünk, ott nagyobb max
75
+ // találattal kell dolgozni, hogy MINDEN fontos elem/szabály MINDIG belekerüljön". A `rules` a legkritikusabb
76
+ // (egy hard-rule kimaradása = rossz döntés), utána a kurált `coding_patterns` / `knowledge` / `memory`. A
77
+ // `documents`/`codebase` marad az 5-ös alapon (ott a nagy korpusz miatt a szűk top-N a helyes — a `depth:'deep'`
78
+ // (`read.deepTopK`) adja a mély feltárást).
79
+ defaultsByTable: {
80
+ [fam_table_type_enum_1.FAM_Table.rules]: 30,
81
+ [fam_table_type_enum_1.FAM_Table.codingPatterns]: 20,
82
+ [fam_table_type_enum_1.FAM_Table.knowledge]: 15,
83
+ [fam_table_type_enum_1.FAM_Table.memory]: 15,
84
+ },
85
+ description: 'Alap visszaadott találat-szám (CCAP DEFAULT_TOP_K=5). **Per-tár default** (user-FR 2026-07-28): '
86
+ + '`rules` 30 · `coding_patterns` 20 · `knowledge`/`memory` 15 — hogy a FONTOS elemek (kanonikus szabályok, '
87
+ + 'kurált minták/tanulságok) MIND beleférjenek, ne csak a top-5. A `documents`/`codebase` marad 5 (nagy '
88
+ + 'korpusz → szűk top-N; a mély feltárás a `depth:"deep"` / `read.deepTopK`). Bármely EXPLICIT beállítás '
89
+ + '(scope/table/global) felülírja a per-tár default-ot.',
75
90
  },
76
91
  'read.deepTopK': {
77
92
  type: 'number', default: 25, min: 1, max: 200, integer: true, levels: ALL_LEVELS,
93
+ // A mély feltárás a FONTOS tárakon még bőségesebb (a normál per-tár topK FÖLÖTT kell maradnia — user-FR 2026-07-28).
94
+ defaultsByTable: {
95
+ [fam_table_type_enum_1.FAM_Table.rules]: 60,
96
+ [fam_table_type_enum_1.FAM_Table.codingPatterns]: 40,
97
+ [fam_table_type_enum_1.FAM_Table.knowledge]: 35,
98
+ [fam_table_type_enum_1.FAM_Table.memory]: 35,
99
+ },
78
100
  description: 'A `depth:"deep"` (mély feltárás) read-mód találat-száma: ennyit ad vissza, ha a query NEM '
79
- + 'ad explicit topK-t. Sokkal több chunk/kontextus egy-egy mély feltáráshoz (a query topK felülírja).',
101
+ + 'ad explicit topK-t. Sokkal több chunk/kontextus egy-egy mély feltáráshoz (a query topK felülírja). '
102
+ + '**Per-tár default:** `rules` 60 · `coding_patterns` 40 · `knowledge`/`memory` 35 — a fontos tárak mély '
103
+ + 'feltárása a normál per-tár topK FÖLÖTT maradjon.',
80
104
  },
81
105
  'read.maxResults': {
82
106
  type: 'number', default: 50, min: 1, max: 500, integer: true, levels: ALL_LEVELS,
@@ -84,6 +108,14 @@ exports.CONFIG_CATALOG = {
84
108
  },
85
109
  'read.maxTokens': {
86
110
  type: 'number', default: 4000, min: 256, max: 32000, integer: true, levels: ALL_LEVELS,
111
+ // A megemelt per-tár topK ÖNMAGÁBAN nem elég: a token-budget levágná a többletet (a 30 szabály ~15k char ≈
112
+ // 3,7k token → a 4000-es budget pont elvágná). A fontos tárakon ezért a budget is arányosan nő (user-FR 2026-07-28).
113
+ defaultsByTable: {
114
+ [fam_table_type_enum_1.FAM_Table.rules]: 12000,
115
+ [fam_table_type_enum_1.FAM_Table.codingPatterns]: 8000,
116
+ [fam_table_type_enum_1.FAM_Table.knowledge]: 8000,
117
+ [fam_table_type_enum_1.FAM_Table.memory]: 8000,
118
+ },
87
119
  description: 'Az agent context védelme + a read-válasz INLINE-tartása (max token; ~4 char/token). A read-tool '
88
120
  + 'ÉRVÉNYESÍTI (FAM-REV: korábban dead-config volt → korlátlan hit-tartalom → a host fájlba mentette a választ). '
89
121
  + 'A budget felett a hit-tartalom levágódik + a túlcsorduló hit-ek elhagyva (`truncated:true`).',
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAM_RemoteConfig_Util = void 0;
4
+ const fs_1 = require("fs");
5
+ const os_1 = require("os");
6
+ const path_1 = require("path");
7
+ /**
8
+ * `FAM_RemoteConfig_Util` — a gép-lokális `~/.fam/config.json` olvasása/írása + a TÁVOLI ELÉRÉS feloldása
9
+ * (user-FR 2026-07-28: „kéne építsünk a FAM-hoz beállítást, hogy távoli elérésként is működhessen — pl. virtuális
10
+ * gépről a host-ra kapcsolva, hogy a host futtat, de a VM is eléri, más címen/útvonalon").
11
+ *
12
+ * **Miért fájl és nem (új) env-var?** Az env csak abban a process-fában él, ahol beállították — a CC-hookok, az MCP-
13
+ * adapter és a CLI KÜLÖN process-ek, így env-vel mindegyiket külön kéne felkonfigurálni (és a workspace-guardrail is
14
+ * tiltja az új env-kulcsok szaporítását). A fájl EGY helyen, perzisztensen, minden hívónak érvényes. A MEGLÉVŐ
15
+ * `FAM_SERVER_URL` env továbbra is működik és ERŐSEBB a fájlnál (ad-hoc felülírás).
16
+ *
17
+ * **Precedencia (kliens):** `--server-url` flag > `FAM_SERVER_URL` env > `~/.fam/config.json` > `http://127.0.0.1:39265`.
18
+ * **Precedencia (szerver bind):** `FAM_SERVER_HOST` env > `~/.fam/config.json` > `127.0.0.1`.
19
+ */
20
+ class FAM_RemoteConfig_Util {
21
+ /** A gép-lokális config-fájl útja (`~/.fam/config.json`). */
22
+ static CONFIG_PATH = (0, path_1.join)((0, os_1.homedir)(), '.fam', 'config.json');
23
+ /** A kliens-default (változatlan visszafelé-kompatibilis érték). */
24
+ static DEFAULT_SERVER_URL = 'http://127.0.0.1:39265';
25
+ /**
26
+ * A szerver BIND-default: **loopback**. Biztonsági alapállás — a FAM-nak nincs authja (ADR-004), ezért
27
+ * alapból NEM hallgatózik a hálózaton; a távoli elérés EXPLICIT opt-in (`bindHost`).
28
+ */
29
+ static DEFAULT_BIND_HOST = '127.0.0.1';
30
+ /**
31
+ * A config-fájl beolvasása. Best-effort: hiányzó/olvashatatlan/érvénytelen JSON → üres objektum (a FAM
32
+ * default-okkal indul; egy elrontott fájl SOHA nem akadályozhatja meg a szerver indulását).
33
+ */
34
+ static load() {
35
+ try {
36
+ if (!(0, fs_1.existsSync)(FAM_RemoteConfig_Util.CONFIG_PATH)) {
37
+ return {};
38
+ }
39
+ const parsed = JSON.parse((0, fs_1.readFileSync)(FAM_RemoteConfig_Util.CONFIG_PATH, 'utf8'));
40
+ if (!parsed || typeof parsed !== 'object') {
41
+ return {};
42
+ }
43
+ const raw = parsed;
44
+ return {
45
+ serverUrl: typeof raw.serverUrl === 'string' && raw.serverUrl.trim() ? raw.serverUrl.trim() : undefined,
46
+ bindHost: typeof raw.bindHost === 'string' && raw.bindHost.trim() ? raw.bindHost.trim() : undefined,
47
+ };
48
+ }
49
+ catch {
50
+ return {};
51
+ }
52
+ }
53
+ /** A config-fájl MÓDOSÍTÁSA (merge a meglévővel; a mappát létrehozza). A `null` érték TÖRLI a kulcsot. */
54
+ static save(patch) {
55
+ const current = FAM_RemoteConfig_Util.load();
56
+ const next = { ...current };
57
+ if (patch.serverUrl !== undefined) {
58
+ next.serverUrl = patch.serverUrl === null ? undefined : patch.serverUrl.trim();
59
+ }
60
+ if (patch.bindHost !== undefined) {
61
+ next.bindHost = patch.bindHost === null ? undefined : patch.bindHost.trim();
62
+ }
63
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(FAM_RemoteConfig_Util.CONFIG_PATH), { recursive: true });
64
+ (0, fs_1.writeFileSync)(FAM_RemoteConfig_Util.CONFIG_PATH, `${JSON.stringify(next, null, 4)}\n`, 'utf8');
65
+ return next;
66
+ }
67
+ /**
68
+ * A KLIENS szerver-URL feloldása (PURE, tesztelhető — a bemenetek explicitek).
69
+ * Precedencia: flag > env > fájl > default. A záró `/`-eket levágja (a hívó `${base}/api/...`-t fűz).
70
+ */
71
+ static resolveServerUrl(set) {
72
+ const raw = set.override?.trim()
73
+ || set.env?.trim()
74
+ || set.file?.serverUrl?.trim()
75
+ || FAM_RemoteConfig_Util.DEFAULT_SERVER_URL;
76
+ return raw.replace(/\/+$/, '');
77
+ }
78
+ /**
79
+ * A SZERVER bind-host feloldása (PURE). Precedencia: env > fájl > loopback-default.
80
+ * A `'*'` kényelmi alias a `0.0.0.0`-ra (minden interfész).
81
+ */
82
+ static resolveBindHost(set) {
83
+ const raw = set.env?.trim() || set.file?.bindHost?.trim() || FAM_RemoteConfig_Util.DEFAULT_BIND_HOST;
84
+ return raw === '*' ? '0.0.0.0' : raw;
85
+ }
86
+ /**
87
+ * Loopback-e a bind (PURE)? A nem-loopback bind FIGYELMEZTETÉST érdemel: a FAM auth NÉLKÜLI (ADR-004), így a
88
+ * hálózaton bárki írhatja/olvashatja a teljes korpuszt. Csak megbízható (host-only/VPN) hálón indokolt.
89
+ */
90
+ static isLoopbackHost(host) {
91
+ return host === '127.0.0.1' || host === 'localhost' || host === '::1';
92
+ }
93
+ }
94
+ exports.FAM_RemoteConfig_Util = FAM_RemoteConfig_Util;
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAM_ReadResponseShape_Util = void 0;
4
+ /**
5
+ * `FAM_ReadResponseShape_Util` — a `read` VÁLASZ-FORMÁZÁSA az agent-fogyasztóknak (user-FR 2026-07-29, agent-panaszok:
6
+ * „a FAM túl nagy csomag válaszokat ad" / „formázatlan, tördeletlen egy soros válaszokat kapnak" / „az eredményekből
7
+ * kiolvasható fájl elérési útvonalak nem állnak össze").
8
+ *
9
+ * **Alapelv: NULLA információ-veszteség.** Nem dobunk el tudást — csak (a) a DUPLIKÁCIÓT szüntetjük meg (ugyanaz az
10
+ * útvonal 3 mezőben, ugyanaz az abszolút út a `source`-ban is), (b) az agent számára HASZNÁLHATATLAN belső kulcsokat
11
+ * hagyjuk el (`contentHash` dedup-kulcs, opak `scopeId`-k), (c) a 17-jegyű float-okat értelmes pontosságra kerekítjük.
12
+ * Minden megmaradó tény továbbra is elérhető, csak EGY, egyértelmű helyen.
13
+ *
14
+ * A transzformáció a read-tool KIMENETÉN fut, így az MCP és a REST `/api/read` is ezt kapja; a belső retrieval-DTO
15
+ * (és a kliens/CLI fogyasztói) érintetlenek.
16
+ */
17
+ class FAM_ReadResponseShape_Util {
18
+ /** A score-ok megjelenítési pontossága (0.5530494673617745 → 0.5530) — a rangsorhoz bőven elég. */
19
+ static SCORE_DECIMALS = 4;
20
+ /**
21
+ * Egy hit KOMPAKT, agent-barát alakja. **Útvonal-SSOT (a #3 panasz gyökere):** eddig HÁROM különböző útvonal-mező
22
+ * volt HÁROM különböző bázissal (`sourceFilePath` a scan-root-hoz relatív → az agent nem tudta feloldani;
23
+ * `absolutePath`; `source.repoRelativePath`), ezért „nem álltak össze". Mostantól:
24
+ * - **`path`** — a MEGNYITHATÓ útvonal: abszolút, VAGY a `basePath`-hoz relatív, ha a hívó adott ilyet. EZ a SSOT.
25
+ * - **`repoPath`** — repo-relatív út + repo-név (git-hivatkozáshoz), CSAK ha ismert.
26
+ * A régi mezők (`sourceFilePath`/`absolutePath`) MEGMARADNAK a visszafelé-kompatibilitásért, de a `path` az elsődleges.
27
+ */
28
+ static compactHit(hit) {
29
+ const source = hit.source;
30
+ const round = (value) => typeof value === 'number' ? Number(value.toFixed(FAM_ReadResponseShape_Util.SCORE_DECIMALS)) : undefined;
31
+ const compact = {
32
+ id: hit.id,
33
+ table: hit.table,
34
+ // ÚTVONAL-SSOT: a `sourceFilePath` már a hívó `basePath`-jához igazított (rebaseToBasePath), különben az
35
+ // abszolút út — így MINDIG feloldható. A régi mezőket lentebb megtartjuk (kompatibilitás).
36
+ path: hit.sourceFilePath ?? hit.absolutePath ?? null,
37
+ score: round(hit.score),
38
+ finalScore: round(hit.finalScore),
39
+ };
40
+ if (hit.lexicalScore !== undefined) {
41
+ compact.lexicalScore = round(hit.lexicalScore);
42
+ }
43
+ if (hit.weight !== undefined && hit.weight !== 1) {
44
+ compact.weight = hit.weight;
45
+ }
46
+ // Lokáció a fájlon BELÜL (melyik szekció / hányadik sor) — a „hol nézzem meg" kérdésre.
47
+ if (hit.headingPath?.length) {
48
+ compact.heading = hit.headingPath.join(' › ');
49
+ }
50
+ const position = hit.position;
51
+ if (position?.lineStart) {
52
+ compact.lines = position.lineEnd && position.lineEnd !== position.lineStart
53
+ ? `${position.lineStart}-${position.lineEnd}`
54
+ : String(position.lineStart);
55
+ }
56
+ if (hit.chunkType) {
57
+ compact.chunkType = hit.chunkType;
58
+ }
59
+ if (hit.kind) {
60
+ compact.kind = hit.kind;
61
+ }
62
+ if (hit.tags?.length) {
63
+ compact.tags = hit.tags;
64
+ }
65
+ // Scope: az opak `scopeId`-k helyett ember/agent-olvasható `layer=név` lánc (a szűkítéshez ezt kell megadni).
66
+ const scopePath = (hit.scopePath ?? []);
67
+ if (scopePath.length) {
68
+ compact.scope = scopePath.map((ref) => `${ref.layer}=${ref.canonicalName}`).join('/');
69
+ }
70
+ // Repo-hivatkozás (git-link) — csak ha van; a `source` többi mezője (root/path/absolutePath) DUPLIKÁCIÓ volt.
71
+ if (source?.repoName && source?.repoRelativePath) {
72
+ compact.repoPath = `${source.repoName}:${source.repoRelativePath}`;
73
+ }
74
+ if (source?.type) {
75
+ compact.sourceType = source.type;
76
+ }
77
+ // Rules-specifikus kurátor-mezők (ha vannak) — ezek döntik el, MENNYIRE kötelező a szabály.
78
+ for (const key of ['ruleId', 'ruleScope', 'importance', 'userApproved', 'category']) {
79
+ const value = hit[key];
80
+ if (value !== undefined && value !== null) {
81
+ compact[key] = value;
82
+ }
83
+ }
84
+ if (hit.lastModifiedMs) {
85
+ compact.lastModified = new Date(hit.lastModifiedMs).toISOString().slice(0, 10);
86
+ }
87
+ // A TARTALOM a végére — így a metaadat-fej egyben olvasható a hosszú szöveg előtt.
88
+ if (hit.content !== undefined) {
89
+ compact.content = hit.content;
90
+ }
91
+ // Visszafelé-kompatibilitás: a `sourceFilePath` MEGMARAD (a kliens `ma-search` erre a mezőnévre épül) — értéke
92
+ // AZONOS a `path`-szal, tehát alias, nem harmadik igazság. Az `absolutePath` viszont CSAK akkor kerül bele, ha
93
+ // ténylegesen ELTÉR (azaz `basePath`-rebase történt, és így külön információt hordoz) — különben szó szerinti
94
+ // duplikáció lenne, ami épp a panaszolt „nagy csomag" + „az útvonalak nem állnak össze" problémát okozta.
95
+ compact.sourceFilePath = hit.sourceFilePath ?? null;
96
+ if (hit.absolutePath && hit.absolutePath !== compact.path) {
97
+ compact.absolutePath = hit.absolutePath;
98
+ }
99
+ return compact;
100
+ }
101
+ /**
102
+ * OPERATÍV VISSZAJELZÉS (user-FR 2026-07-29 — általános MCP-követelmény): „a tool-ok adjanak visszajelzést a
103
+ * működésről: összesen N elem között kerestünk, M tűnik relevánsnak, K megjelenítve + mit tegyél, hogy mindet
104
+ * lásd / fájlba írd / célzottabban keress". PURE — a számokból épít egy mondatot + konkrét, MÁSOLHATÓ tippeket.
105
+ */
106
+ static buildSummary(set) {
107
+ const scope = set.tables.length ? set.tables.join('+') : 'minden tár';
108
+ const searched = set.searchedCount > 0
109
+ ? `${FAM_ReadResponseShape_Util.humanCount(set.searchedCount)} elem között kerestünk`
110
+ : 'a keresés lefutott';
111
+ const relevant = `${set.totalRelevant} tűnik magabiztosan relevánsnak`;
112
+ const shown = set.truncated
113
+ ? `${set.shown} megjelenítve (topK=${set.topK} vágta)`
114
+ : `mind a ${set.shown} megjelenítve`;
115
+ return `[${scope}] ${searched} · ${relevant} · ${shown}.`;
116
+ }
117
+ /** A summary melletti, KONKRÉT következő lépések (csak a releváns tippek — nincs zaj). PURE. */
118
+ static buildHints(set) {
119
+ const hints = [];
120
+ if (set.truncated) {
121
+ const all = Math.max(set.totalRelevant, set.shown);
122
+ hints.push(`MIND: futtasd újra \`topK: ${all}\`-val (vagy \`depth: "deep"\`-pel a mélyebb feltárásért).`);
123
+ hints.push('FÁJLBA: a CLI-vel `fam read --query "…" --json > talalatok.json` (nagy találat-halmaznál ez '
124
+ + 'a javasolt út — a chat-választ nem terheli).');
125
+ }
126
+ if (set.totalRelevant > set.shown * 3 && !set.hasScopeFilter) {
127
+ hints.push('CÉLZOTTABBAN: szűkíts `scope`-pal (pl. `{"layer":"project","name":"<projekt>"}`), `tags`-szel '
128
+ + 'vagy `kind`-dal — a sok találat általában túl tág kérdést jelez.');
129
+ }
130
+ if (!set.totalRelevant) {
131
+ hints.push('NINCS magabiztos találat: próbálj más megfogalmazást/kulcsszavakat, vagy emeld a `topK`-t; '
132
+ + 'a `minScore: 0` a leggyengébb tippeket is visszaadja.');
133
+ }
134
+ return hints;
135
+ }
136
+ /** Rövid, ember-olvasható darabszám (12 400 → „12.4k"). PURE. */
137
+ static humanCount(count) {
138
+ if (count < 1000) {
139
+ return String(count);
140
+ }
141
+ if (count < 1000000) {
142
+ const thousands = count / 1000;
143
+ return `${thousands >= 100 ? Math.round(thousands) : Number(thousands.toFixed(1))}k`;
144
+ }
145
+ return `${Number((count / 1000000).toFixed(1))}M`;
146
+ }
147
+ /** Egy teljes result-blokk formázása: kompakt hit-ek + summary + hints (a result többi mezője érintetlen). */
148
+ static shapeResult(set) {
149
+ const hits = set.result.hits ?? [];
150
+ const shaped = { ...set.result };
151
+ shaped.summary = FAM_ReadResponseShape_Util.buildSummary({
152
+ tables: set.tables, searchedCount: set.searchedCount, totalRelevant: set.result.totalRelevant ?? 0,
153
+ shown: hits.length, truncated: Boolean(set.result.truncated), topK: set.topK,
154
+ });
155
+ const hints = FAM_ReadResponseShape_Util.buildHints({
156
+ totalRelevant: set.result.totalRelevant ?? 0, shown: hits.length,
157
+ truncated: Boolean(set.result.truncated), hasScopeFilter: Boolean(set.hasScopeFilter),
158
+ });
159
+ if (hints.length) {
160
+ shaped.hints = hints;
161
+ }
162
+ shaped.searchedCount = set.searchedCount;
163
+ shaped.hits = hits.map((hit) => FAM_ReadResponseShape_Util.compactHit(hit));
164
+ return shaped;
165
+ }
166
+ }
167
+ exports.FAM_ReadResponseShape_Util = FAM_ReadResponseShape_Util;
@@ -110,13 +110,18 @@ class FAM_Mcp_Adapter_Service {
110
110
  }
111
111
  return 'Ismeretlen hiba a tool-végrehajtás során.';
112
112
  }
113
- /** JSON-stringify (a transport text-content payload-ja). */
113
+ /**
114
+ * JSON-stringify (a transport text-content payload-ja) — **TÖRDELVE** (user-FR 2026-07-29, agent-panasz:
115
+ * „formázatlan, tördeletlen egy soros válaszokat kapnak"). A 2-szóközös indentálás soronként EGY mezőt ad, így a
116
+ * válasz olvasható/diffelhető; a token-költség elhanyagolható a beágyazott `content`-ek mellett, cserébe az agent
117
+ * (és a `truncate`-elő host) sokkal jobban tudja kezelni.
118
+ */
114
119
  stringify(data) {
115
120
  try {
116
- return JSON.stringify(data);
121
+ return JSON.stringify(data, null, 2);
117
122
  }
118
123
  catch {
119
- return JSON.stringify({ ok: false, error: { message: 'A válasz nem szerializálható JSON-ra.' } });
124
+ return JSON.stringify({ ok: false, error: { message: 'A válasz nem szerializálható JSON-ra.' } }, null, 2);
120
125
  }
121
126
  }
122
127
  }