@futdevpro/fdp-agent-memory 1.1.12 → 1.1.14

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.12",
3
+ "version": "1.1.14",
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": {
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FAM_Serve_Util = void 0;
4
+ const tslib_1 = require("tslib");
4
5
  const child_process_1 = require("child_process");
6
+ const net = tslib_1.__importStar(require("net"));
5
7
  const fs_1 = require("fs");
6
8
  const os_1 = require("os");
7
9
  const path_1 = require("path");
@@ -147,7 +149,13 @@ class FAM_Serve_Util {
147
149
  if (action === 'restart') {
148
150
  fam_output_util_1.FAM_Output_Util.logInfo(globals, `FAM szerver RESTART: a(z) ${port} porton futó szerver leállítása…`);
149
151
  await FAM_Serve_Util.stopServerOnPort(port);
150
- await FAM_Serve_Util.waitForPortDown(client, 8000);
152
+ // A health-down NEM elég: az új szerver bind-preflight-je a TÉNYLEGESEN SZABAD (bindolható) portot
153
+ // igényli (a kill utáni process-halál + TIME_WAIT lassú lehet) → a port bindolhatóságát várjuk.
154
+ const freed = await FAM_Serve_Util.waitForPortFree(port, 12000);
155
+ if (!freed) {
156
+ fam_output_util_1.FAM_Output_Util.logInfo(globals, `⚠ a(z) ${port} port 12s alatt nem szabadult fel — az indítás megpróbálom, `
157
+ + 'de ha bind-hiba van, futtasd újra a `fam serve --restart`-ot.');
158
+ }
151
159
  }
152
160
  return FAM_Serve_Util.spawnServer({ globals: globals, local: local, port: port, healthUrl: healthUrl, client: client });
153
161
  }
@@ -265,14 +273,24 @@ class FAM_Serve_Util {
265
273
  return false;
266
274
  }
267
275
  /** A port LE-állásának bevárása (a `--restart` kill után, mielőtt újraindítunk), felső korláttal. */
268
- static async waitForPortDown(client, timeoutMs) {
276
+ static async waitForPortFree(port, timeoutMs) {
269
277
  const deadline = Date.now() + timeoutMs;
270
278
  while (Date.now() < deadline) {
271
- if (!(await client.isReachable(1000))) {
272
- return;
279
+ if (await FAM_Serve_Util.isPortFree(port)) {
280
+ return true;
273
281
  }
274
282
  await FAM_Serve_Util.sleep(FAM_Serve_Util.HEALTH_POLL_MS);
275
283
  }
284
+ return false;
285
+ }
286
+ /** Bindolható-e (szabad-e) a port? Egy próba-`listen` a loopbackon, azonnali close — `true`, ha köthető. */
287
+ static isPortFree(port) {
288
+ return new Promise((resolveCheck) => {
289
+ const tester = net.createServer();
290
+ tester.once('error', () => resolveCheck(false));
291
+ tester.once('listening', () => tester.close(() => resolveCheck(true)));
292
+ tester.listen(port, '127.0.0.1');
293
+ });
276
294
  }
277
295
  /**
278
296
  * A megadott porton FIGYELŐ process leállítása (a `--restart` magja). Platform-natív, fail-safe: Windows →
@@ -284,7 +302,12 @@ class FAM_Serve_Util {
284
302
  const command = isWin ? (process.env.ComSpec ?? 'powershell.exe') : 'sh';
285
303
  const psScript = `$ErrorActionPreference='SilentlyContinue';`
286
304
  + `(Get-NetTCPConnection -LocalPort ${port} -State Listen).OwningProcess | Select-Object -Unique | `
287
- + `ForEach-Object { Stop-Process -Id $_ -Force }`;
305
+ + `ForEach-Object { Stop-Process -Id $_ -Force };`
306
+ // A serve-ablakot (a fam-server-<port>.ps1-t futtató powershell) is leállítjuk → nincs árva,
307
+ // „[FAM szerver kilépett]" zombi-ablak a restart után (különben halmozódnak).
308
+ + `Get-CimInstance Win32_Process -Filter "Name='powershell.exe'" | `
309
+ + `Where-Object { $_.CommandLine -like '*fam-server-${port}*' } | `
310
+ + `ForEach-Object { Stop-Process -Id $_.ProcessId -Force }`;
288
311
  const args = isWin
289
312
  ? ['-NoProfile', '-Command', psScript]
290
313
  : ['-c', `lsof -ti tcp:${port} | xargs -r kill -9`];
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAM_GitRepo_Util = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("fs"));
6
+ const path = tslib_1.__importStar(require("path"));
7
+ /**
8
+ * `FAM_GitRepo_Util` (user-FR 2026-06-21) — egy scannelt fájlhoz felderíti a **git-repo provenance-t**:
9
+ * a repo-gyökeret (`.git` felfelé keresve), a **GitHub/remote URL-t** (`.git/config` `remote.origin.url`,
10
+ * normalizálva), a **branch-et** (`.git/HEAD`) és a **repo-gyökérhez relatív utat**. **Függőség-mentes**
11
+ * (NEM `git`-parancs — közvetlenül a `.git/` fájlokból olvas; gyors + offline). A scan **repoRoot-onként cache-eli**
12
+ * (egy mappa-scan ~1 repo → egyszer olvas), így a per-fájl költség elhanyagolható.
13
+ *
14
+ * Miért így (a user „relative path + GitHub repo" döntése): a repo-URL + a **REPO-relatív** út KANONIKUS,
15
+ * gép-független, kattintható citáció (`<repoUrl>/blob/<branch>/<repoRelativePath>`) — szemben a gép-specifikus
16
+ * abszolút úttal vagy a törékeny „projektnévből kiszámolt" relatív úttal.
17
+ */
18
+ class FAM_GitRepo_Util {
19
+ /** Meddig megyünk felfelé `.git`-et keresve (biztonsági korlát a végtelen ciklus ellen). */
20
+ static MAX_WALK_UP = 40;
21
+ /** Belső repoRoot→info cache (a scan-során a `.git` nem változik; egy repo → egyszer olvas). */
22
+ static _cache = new Map();
23
+ /**
24
+ * Egy scannelt fájl **scan-source git-mezői** (a `FAM_Source`-ba spread-elve): `repoUrl` / `repoName` /
25
+ * `repoRelativePath` (a repo-gyökérhez) / `repoBranch`. Cache-elt (belső statikus Map) + fail-safe (üres
26
+ * objektum, ha a fájl NINCS git-repóban / hiba) — a scan SOHA nem bukik el a git-felderítésen.
27
+ */
28
+ static sourceFieldsFor(absFilePath) {
29
+ try {
30
+ const info = FAM_GitRepo_Util.resolve(absFilePath, FAM_GitRepo_Util._cache);
31
+ if (!info) {
32
+ return {};
33
+ }
34
+ return {
35
+ repoUrl: info.repoUrl,
36
+ repoName: info.repoName,
37
+ repoRelativePath: FAM_GitRepo_Util.relativePath(absFilePath, info.repoRoot),
38
+ repoBranch: info.repoBranch,
39
+ };
40
+ }
41
+ catch {
42
+ return {};
43
+ }
44
+ }
45
+ /**
46
+ * A teljes repo-info felderítése egy fájl abszolút útjából, **cache-elve a repoRoot-on** (a hívó adja a
47
+ * Map-et; egy scan végig egyet használ). `null`, ha a fájl NINCS git-repóban. A relatív utat a hívó a
48
+ * `relativePath`-szal számolja (a repoRoot ismeretében).
49
+ */
50
+ static resolve(absFilePath, cache) {
51
+ const repoRoot = FAM_GitRepo_Util.findRepoRoot(absFilePath);
52
+ if (!repoRoot) {
53
+ return null;
54
+ }
55
+ const cached = cache.get(repoRoot);
56
+ if (cached !== undefined) {
57
+ return cached;
58
+ }
59
+ const remote = FAM_GitRepo_Util.readRemoteUrl(repoRoot);
60
+ const info = {
61
+ repoRoot: repoRoot,
62
+ repoUrl: remote?.url,
63
+ repoName: remote?.name,
64
+ repoBranch: FAM_GitRepo_Util.readBranch(repoRoot),
65
+ };
66
+ cache.set(repoRoot, info);
67
+ return info;
68
+ }
69
+ /** A repo-gyökér keresése felfelé (`.git` mappa VAGY fájl — a submodule `.git` fájl). `null`, ha nincs. */
70
+ static findRepoRoot(absStartPath) {
71
+ let dir = absStartPath;
72
+ try {
73
+ // Ha a start egy fájl, a könyvtárából indulunk; ha mappa, magából.
74
+ if (fs.existsSync(absStartPath) && fs.statSync(absStartPath).isFile()) {
75
+ dir = path.dirname(absStartPath);
76
+ }
77
+ }
78
+ catch {
79
+ dir = path.dirname(absStartPath);
80
+ }
81
+ for (let i = 0; i < FAM_GitRepo_Util.MAX_WALK_UP; i++) {
82
+ if (fs.existsSync(path.join(dir, '.git'))) {
83
+ return dir;
84
+ }
85
+ const parent = path.dirname(dir);
86
+ if (parent === dir) {
87
+ return null; // elértük a filesystem-gyökeret
88
+ }
89
+ dir = parent;
90
+ }
91
+ return null;
92
+ }
93
+ /**
94
+ * A `remote.origin.url` kiolvasása a `.git/config`-ból + normalizálva (`https://github.com/owner/repo`).
95
+ * `undefined`, ha nincs origin / nem olvasható. (A submodule `.git` FÁJL `gitdir:`-re mutat — ezt is követjük.)
96
+ */
97
+ static readRemoteUrl(repoRoot) {
98
+ try {
99
+ const gitPath = path.join(repoRoot, '.git');
100
+ const configPath = fs.statSync(gitPath).isDirectory()
101
+ ? path.join(gitPath, 'config')
102
+ : path.join(FAM_GitRepo_Util.resolveGitdir(repoRoot, gitPath), 'config');
103
+ if (!fs.existsSync(configPath)) {
104
+ return undefined;
105
+ }
106
+ const raw = fs.readFileSync(configPath, 'utf-8');
107
+ const rawUrl = FAM_GitRepo_Util.extractOriginUrl(raw);
108
+ return rawUrl ? FAM_GitRepo_Util.normalizeRepoUrl(rawUrl) : undefined;
109
+ }
110
+ catch {
111
+ return undefined;
112
+ }
113
+ }
114
+ /** A `.git/HEAD`-ből az aktuális branch (`ref: refs/heads/<branch>`); detached SHA / hiba → `undefined`. */
115
+ static readBranch(repoRoot) {
116
+ try {
117
+ const gitPath = path.join(repoRoot, '.git');
118
+ const headPath = fs.statSync(gitPath).isDirectory()
119
+ ? path.join(gitPath, 'HEAD')
120
+ : path.join(FAM_GitRepo_Util.resolveGitdir(repoRoot, gitPath), 'HEAD');
121
+ const head = fs.readFileSync(headPath, 'utf-8').trim();
122
+ const match = head.match(/^ref:\s*refs\/heads\/(.+)$/);
123
+ return match ? match[1].trim() : undefined;
124
+ }
125
+ catch {
126
+ return undefined;
127
+ }
128
+ }
129
+ /** A fájl REPO-gyökérhez relatív útja (POSIX-szeparátorral, a kattintható citációhoz). */
130
+ static relativePath(absFilePath, repoRoot) {
131
+ return path.relative(repoRoot, absFilePath).replace(/\\/g, '/');
132
+ }
133
+ /**
134
+ * A nyers remote-URL normalizálása kattintható HTTPS-formára + `owner/repo` névre. Lefedi:
135
+ * `git@host:owner/repo.git`, `https://host/owner/repo.git`, `ssh://git@host/owner/repo.git`. A `.git`-suffix
136
+ * + a beágyazott credential levágva. Nem-github host esetén a host marad (gitlab/bitbucket is kattintható).
137
+ */
138
+ static normalizeRepoUrl(rawUrl) {
139
+ let url = rawUrl.trim();
140
+ // scp-szerű: git@host:owner/repo(.git) → https://host/owner/repo
141
+ const scp = url.match(/^[\w.-]+@([\w.-]+):(.+)$/);
142
+ if (scp) {
143
+ url = `https://${scp[1]}/${scp[2]}`;
144
+ }
145
+ url = url
146
+ .replace(/^ssh:\/\/(?:[\w.-]+@)?/, 'https://')
147
+ .replace(/^git:\/\//, 'https://')
148
+ .replace(/^https?:\/\/[^@/]+@/, 'https://') // beágyazott credential levágása
149
+ .replace(/\.git$/, '')
150
+ .replace(/\/+$/, '');
151
+ const nameMatch = url.match(/^https?:\/\/[\w.-]+\/(.+?\/[^/]+)$/);
152
+ return { url: url, name: nameMatch ? nameMatch[1] : undefined };
153
+ }
154
+ /** A `[remote "origin"]` szekció `url = …` sora a `.git/config`-ból (defenzív, szekció-tudatos). */
155
+ static extractOriginUrl(configText) {
156
+ let inOrigin = false;
157
+ for (const rawLine of configText.split(/\r?\n/)) {
158
+ const line = rawLine.trim();
159
+ const section = line.match(/^\[remote\s+"([^"]+)"\]$/);
160
+ if (section) {
161
+ inOrigin = section[1] === 'origin';
162
+ continue;
163
+ }
164
+ if (line.startsWith('[')) {
165
+ inOrigin = false;
166
+ continue;
167
+ }
168
+ if (inOrigin) {
169
+ const url = line.match(/^url\s*=\s*(.+)$/);
170
+ if (url) {
171
+ return url[1].trim();
172
+ }
173
+ }
174
+ }
175
+ return undefined;
176
+ }
177
+ /** A submodule `.git` FÁJL (`gitdir: <relpath>`) feloldása a tényleges git-könyvtárra. */
178
+ static resolveGitdir(repoRoot, gitFilePath) {
179
+ try {
180
+ const content = fs.readFileSync(gitFilePath, 'utf-8').trim();
181
+ const match = content.match(/^gitdir:\s*(.+)$/);
182
+ if (match) {
183
+ const target = match[1].trim();
184
+ return path.isAbsolute(target) ? target : path.resolve(repoRoot, target);
185
+ }
186
+ }
187
+ catch {
188
+ /* fall through */
189
+ }
190
+ return gitFilePath;
191
+ }
192
+ }
193
+ exports.FAM_GitRepo_Util = FAM_GitRepo_Util;
@@ -15,6 +15,7 @@ const fam_reference_code_util_1 = require("../../../_collections/fam-reference-c
15
15
  const embedding_1 = require("../../embedding");
16
16
  const scope_reference_1 = require("../../scope-reference");
17
17
  const fam_content_hash_util_1 = require("../_collections/fam-content-hash.util");
18
+ const fam_git_repo_util_1 = require("../_collections/fam-git-repo.util");
18
19
  const fam_project_identity_util_1 = require("../_collections/fam-project-identity.util");
19
20
  const fam_scan_path_util_1 = require("../_collections/fam-scan-path.util");
20
21
  const fam_scan_progress_util_1 = require("../_collections/fam-scan-progress.util");
@@ -408,6 +409,8 @@ class FAM_Ingest_ControlService {
408
409
  headingPath: set.chunk.headingPath,
409
410
  source: {
410
411
  type: 'scan', path: set.file.relativePath, root: set.root, absolutePath: set.file.absolutePath,
412
+ // Git-repo provenance (user-FR): repoUrl + repoRelativePath + repoBranch → kattintható citáció.
413
+ ...fam_git_repo_util_1.FAM_GitRepo_Util.sourceFieldsFor(set.file.absolutePath),
411
414
  },
412
415
  addedBy: 'scan',
413
416
  ingestRunId: set.runId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@futdevpro/fdp-agent-memory",
3
- "version": "1.1.12",
3
+ "version": "1.1.14",
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": {