@futdevpro/fdp-agent-memory 0.1.0 → 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.
Files changed (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -7
  3. package/build/package.json +6 -5
  4. package/build/src/_cli/_collections/fam-arg.util.js +48 -0
  5. package/build/src/_cli/_collections/fam-cli.const.js +40 -0
  6. package/build/src/_cli/_collections/fam-output.util.js +86 -0
  7. package/build/src/_cli/_collections/fam-project-discovery.util.js +98 -0
  8. package/build/src/_cli/_commands/capture.command.js +73 -0
  9. package/build/src/_cli/_commands/config.command.js +93 -0
  10. package/build/src/_cli/_commands/doctor.command.js +124 -0
  11. package/build/src/_cli/_commands/errors.command.js +66 -0
  12. package/build/src/_cli/_commands/export.command.js +65 -0
  13. package/build/src/_cli/_commands/find-duplicates.command.js +97 -0
  14. package/build/src/_cli/_commands/import.command.js +136 -0
  15. package/build/src/_cli/_commands/init.command.js +147 -0
  16. package/build/src/_cli/_commands/read.command.js +109 -0
  17. package/build/src/_cli/_commands/scan-projects.command.js +138 -0
  18. package/build/src/_cli/_commands/scan.command.js +98 -0
  19. package/build/src/_cli/_commands/seed.command.js +40 -0
  20. package/build/src/_cli/_commands/serve.command.js +373 -0
  21. package/build/src/_cli/_commands/start.command.js +134 -0
  22. package/build/src/_cli/_commands/stats.command.js +54 -0
  23. package/build/src/_cli/_commands/write.command.js +103 -0
  24. package/build/src/_cli/_models/interfaces/fam-cli-global-options.interface.js +2 -0
  25. package/build/src/_cli/_models/interfaces/fam-cli-output.interface.js +9 -0
  26. package/build/src/_cli/_models/interfaces/fam-client-result.interface.js +2 -0
  27. package/build/src/_cli/_services/fam-client.service.js +140 -0
  28. package/build/src/_cli/register-commands.js +86 -0
  29. package/build/src/_collections/config-catalog.const.js +67 -1
  30. package/build/src/_collections/fam-console.util.js +367 -0
  31. package/build/src/_collections/fam-entry-bootstrap.util.js +158 -4
  32. package/build/src/_collections/fam-error-factory.util.js +0 -9
  33. package/build/src/_collections/fam-mcp-bridge.util.js +49 -0
  34. package/build/src/_collections/fam-reference-code.util.js +105 -0
  35. package/build/src/_collections/fam-version.const.js +10 -0
  36. package/build/src/_models/data-models/fam-entry-base-properties.const.js +1 -0
  37. package/build/src/_models/data-models/fam-entry.data-model.js +6 -0
  38. package/build/src/_models/data-models/fam-ingest-run.data-model.js +3 -1
  39. package/build/src/_models/data-models/fam-reference.data-model.js +7 -0
  40. package/build/src/_modules/capture/_collections/fam-capture.const.js +11 -0
  41. package/build/src/_modules/capture/_services/fam-auto-capture.control-service.js +87 -0
  42. package/build/src/_modules/capture/index.js +8 -0
  43. package/build/src/_modules/embedding/_collections/fam-embedding-prefix.util.js +77 -0
  44. package/build/src/_modules/embedding/_services/fam-duplicate-scan.control-service.js +202 -0
  45. package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +33 -9
  46. package/build/src/_modules/embedding/_services/fam-embedding.control-service.js +21 -2
  47. package/build/src/_modules/embedding/_services/fam-entry.data-service.js +135 -0
  48. package/build/src/_modules/embedding/_services/fam-vector-search.control-service.js +42 -32
  49. package/build/src/_modules/embedding/index.js +4 -1
  50. package/build/src/_modules/export/_collections/fam-export.const.js +22 -0
  51. package/build/src/_modules/export/_services/fam-export.control-service.js +64 -0
  52. package/build/src/_modules/export/index.js +8 -0
  53. package/build/src/_modules/ingest/_collections/fam-famignore.util.js +83 -0
  54. package/build/src/_modules/ingest/_collections/fam-file-routing.util.js +59 -48
  55. package/build/src/_modules/ingest/_collections/fam-git-repo.util.js +193 -0
  56. package/build/src/_modules/ingest/_collections/fam-project-identity.util.js +134 -0
  57. package/build/src/_modules/ingest/_collections/fam-scan-progress.util.js +57 -0
  58. package/build/src/_modules/ingest/_collections/fam-scan-summary.util.js +60 -0
  59. package/build/src/_modules/ingest/_collections/fam-scan-weight.util.js +53 -0
  60. package/build/src/_modules/ingest/_collections/fam-secret-exclude.util.js +37 -14
  61. package/build/src/_modules/ingest/_collections/fam-sliding-chunker.util.js +34 -0
  62. package/build/src/_modules/ingest/_collections/fam-ts-chunker.util.js +200 -14
  63. package/build/src/_modules/ingest/_services/fam-delta-compare.util.js +4 -1
  64. package/build/src/_modules/ingest/_services/fam-ingest-run.data-service.js +7 -4
  65. package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +349 -17
  66. package/build/src/_modules/ingest/_services/fam-scan.control-service.js +25 -2
  67. package/build/src/_modules/ingest/index.js +3 -1
  68. package/build/src/_modules/mcp/_collections/fam-active-rules.util.js +56 -0
  69. package/build/src/_modules/mcp/_collections/fam-core-tools.const.js +47 -6
  70. package/build/src/_modules/mcp/_services/fam-capabilities-tool.service.js +4 -4
  71. package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +224 -18
  72. package/build/src/_modules/mcp/_services/fam-mcp-adapter.service.js +4 -4
  73. package/build/src/_modules/mcp/_services/fam-mcp-server.service.js +4 -4
  74. package/build/src/_modules/mcp/_services/fam-read-tool.service.js +53 -1
  75. package/build/src/_modules/mcp/_services/fam-write-tool.service.js +104 -8
  76. package/build/src/_modules/mcp/index.js +4 -4
  77. package/build/src/_modules/migration/_collections/fam-claude-mem-normalize.util.js +66 -3
  78. package/build/src/_modules/migration/_collections/fam-prompt-aggregate.util.js +143 -0
  79. package/build/src/_modules/migration/_collections/fam-target-mapping.util.js +19 -0
  80. package/build/src/_modules/migration/_enums/fam-claude-mem-source.type-enum.js +6 -0
  81. package/build/src/_modules/migration/_models/interfaces/fam-claude-mem.interface.js +5 -0
  82. package/build/src/_modules/migration/_services/fam-agent-memory-reader.service.js +125 -0
  83. package/build/src/_modules/migration/_services/fam-claude-mem-import.control-service.js +101 -18
  84. package/build/src/_modules/migration/_services/fam-import-dedup.data-service.js +53 -0
  85. package/build/src/_modules/migration/index.js +3 -1
  86. package/build/src/_modules/retrieval/_services/fam-retrieval-candidate.data-service.js +78 -4
  87. package/build/src/_modules/retrieval/_services/fam-retrieval.control-service.js +293 -50
  88. package/build/src/_modules/scope-reference/_collections/fam-scope-normalize.util.js +6 -3
  89. package/build/src/_modules/scope-reference/_services/fam-reference.data-service.js +18 -0
  90. package/build/src/_modules/scope-reference/_services/fam-scope-resolver.control-service.js +79 -20
  91. package/build/src/_routes/server/api/api.controller.js +34 -2
  92. package/build/src/_routes/server/client-app/client-app.control-service.js +1 -1
  93. package/build/src/_routes/server/server-status/server-status.controller.js +2 -1
  94. package/build/src/app.server.js +13 -1
  95. package/build/src/environments/environment.js +1 -1
  96. package/build/src/index.js +1 -1
  97. package/client-dist/{chunk-GHKRM4SM.js → chunk-I77GXVAQ.js} +1 -1
  98. package/client-dist/{chunk-LMTL7GA3.js → chunk-YXHWCJ5O.js} +1 -1
  99. package/client-dist/index.html +1 -1
  100. package/client-dist/{main-2KWB3QYK.js → main-PJPEDVJT.js} +1 -1
  101. package/package.json +6 -5
@@ -9,77 +9,88 @@ const fam_table_type_enum_1 = require("../../../_enums/fam-table.type-enum");
9
9
  * felderített fájlt a kiterjesztése / fájlnév-mintája alapján `documents` / `codebase` / `skip`
10
10
  * cél-tárra route-ol. Statikus util (nincs DB-/service-függés).
11
11
  *
12
- * Routing-térkép (dsgn-004 §2):
13
- * - `documents` `.md` `.mdx` `.txt` `.rst`
14
- * - `codebase` ← `.html` `.ts` `.tsx` `.js` `.jsx` `.py` `.css` `.scss` `.json` + config-fájlok
15
- * (`*.config.*`, `tsconfig*.json`, `package.json`, `.eslintrc*`, `.prettierrc*`,
16
- * `*.yml`/`*.yaml`/`*.toml`/`*.ini`, `Dockerfile`)
17
- * - `skip` minden egyéb (bináris, kép, lock-fájl, stb.) verdikt nélkül, `filesSkipped`-ben.
18
- *
19
- * **A `.html` markup KIZÁRÓLAG `codebase`** (NEM `documents`) a `documents` route a tartalmi
20
- * dokumentációt célozza (`.md`/`.mdx`/`.txt`/`.rst`), nem a markup-ot (dsgn-004 §2). A
21
- * `chunkStrategyFor` a route + kiterjesztés alapján adja a chunker-stratégiát (SP-4.2 dispatch).
12
+ * **FAM-REV-064 — „semmi ne vesszen el" inverzió:** a korábbi szűk ALLOWLIST (csak ismert kiterjesztés
13
+ * → tár, MINDEN MÁS → skip) szöveges, info-hordozó fájlokat ejtett el (pl. `.mdc` Cursor-rules, `.sh`,
14
+ * `.ps1`, `.xml`, git-hookok, `LICENSE`). Az új logika DENYLIST-alapú: a **dokumentum-kiterjesztések** a
15
+ * `documents` táron; a **known-binary** (kép/font/archív/média/bináris/office) + a **generált zaj** (lock-
16
+ * fájlok, `.min.*`, source-map) `skip`; **MINDEN MÁS szöveges fájl** (ismert VAGY ismeretlen kiterjesztés,
17
+ * extension-nélküli `LICENSE`/`Dockerfile`/git-hook) → `codebase`. A bináris-tartalmú álcázott fájlokat a
18
+ * scanner null-byte tartalom-ellenőrzése szűri (`binary-content` skip), így nem kerül szemét a generic
19
+ * chunkerbe. Így minden valódi szöveg bekerül; a tényleges bináris + a generált zaj kimarad.
22
20
  */
23
21
  class FAM_FileRouting_Util {
24
- /** Dokumentum-kiterjesztések → `documents` (dsgn-004 §2). */
25
- static DOCUMENT_EXTENSIONS = ['.md', '.mdx', '.txt', '.rst'];
26
- /** Kód-/markup-kiterjesztések `codebase` (a `.html` markup IDE tartozik, dsgn-004 §2). */
27
- static CODE_EXTENSIONS = [
28
- '.html', '.ts', '.tsx', '.mts', '.js', '.jsx', '.py', '.css', '.scss', '.json',
29
- '.yml', '.yaml', '.toml', '.ini',
30
- ];
31
- /** Markdown-kiterjesztések (a header-aware chunker célja, dsgn-004 §3.2). */
32
- static MARKDOWN_EXTENSIONS = ['.md', '.mdx'];
33
- /** TS-szemantikus chunker kiterjesztései (dsgn-004 §3.1). */
34
- static TS_EXTENSIONS = ['.ts', '.tsx', '.mts', '.js', '.jsx'];
35
- /** Extension-nélküli config-fájlnevek → `codebase` (dsgn-004 §2). */
36
- static CONFIG_FILENAMES = ['dockerfile'];
22
+ /** Dokumentum-kiterjesztések → `documents` (a `.mdc` Cursor-rules + `.markdown` IDE, FAM-REV-064). */
23
+ static DOCUMENT_EXTENSIONS = ['.md', '.mdx', '.markdown', '.mdc', '.txt', '.text', '.rst', '.adoc'];
24
+ /** Markdown-kiterjesztések (a header-aware chunker célja; a `.mdc`/`.markdown` is, FAM-REV-064). */
25
+ static MARKDOWN_EXTENSIONS = ['.md', '.mdx', '.markdown', '.mdc'];
26
+ /** TS-szemantikus chunker kiterjesztései (dsgn-004 §3.1; `.cjs`/`.cts`/`.mjs` is JS/TS-variáns). */
27
+ static TS_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.cjs', '.mjs'];
28
+ /**
29
+ * Known-binary kiterjesztések `skip` (FAM-REV-064 denylist). Kép/font/archív/média/futtatható/
30
+ * lefordított/office/adatbázis + source-map (`.map`). Az office/PDF szöveg-kinyerés BACKLOG (dsgn BACKLOG),
31
+ * ezért egyelőre skip (nem néma — `filesSkipped` + `skipReason`). NEM tartalmazza a `.svg`-t? — DE: a
32
+ * `.svg` döntően generált path-grafika (alacsony szemantikai szöveg), ezért IDE soroljuk.
33
+ */
34
+ static BINARY_EXTENSIONS = new Set([
35
+ // képek
36
+ '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.webp', '.tiff', '.tif', '.svg', '.psd', '.ai',
37
+ // fontok
38
+ '.woff', '.woff2', '.ttf', '.eot', '.otf',
39
+ // archívok
40
+ '.zip', '.tgz', '.gz', '.tar', '.rar', '.7z', '.bz2', '.xz', '.jar', '.war', '.nupkg',
41
+ // média
42
+ '.mp3', '.mp4', '.wav', '.avi', '.mov', '.mkv', '.flac', '.ogg', '.webm', '.m4a', '.aac',
43
+ // futtatható / lefordított
44
+ '.exe', '.dll', '.so', '.dylib', '.bin', '.o', '.a', '.lib', '.node', '.wasm', '.class', '.pyc', '.pyo',
45
+ // office / PDF (szöveg-kinyerés BACKLOG)
46
+ '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.odt', '.ods',
47
+ // adatbázis / egyéb bináris
48
+ '.db', '.sqlite', '.sqlite3', '.mdb', '.dat', '.map',
49
+ ]);
37
50
  /**
38
- * Egy fájl route-verdiktje (dsgn-004 §2). A kiterjesztés-térkép, majd a config-fájlnév-minták
39
- * (extension-nélküli `Dockerfile`, `*.config.*`, `tsconfig*.json`, `.eslintrc*`, `.prettierrc*`)
40
- * → `codebase`; egyébként `skip`. A vizsgálat case-insensitive a kiterjesztésre/fájlnévre.
51
+ * Egy fájl route-verdiktje (FAM-REV-064 denylist-inverzió). Sorrend: (1) generált-zaj (lock/`.min.`/
52
+ * source-map) → skip; (2) dokumentum-kiterjesztés `documents`; (3) known-binary skip; (4) MINDEN MÁS
53
+ * (bármilyen szöveg, ismeretlen kiterjesztés, extension-nélküli) → `codebase`. Case-insensitive.
41
54
  */
42
55
  static route(relativePath) {
43
56
  const fileName = path.basename(relativePath).toLowerCase();
44
57
  const extension = path.extname(fileName);
58
+ if (FAM_FileRouting_Util.isGeneratedNoise(fileName)) {
59
+ return 'skip';
60
+ }
45
61
  if (FAM_FileRouting_Util.DOCUMENT_EXTENSIONS.includes(extension)) {
46
62
  return fam_table_type_enum_1.FAM_Table.documents;
47
63
  }
48
- if (FAM_FileRouting_Util.CODE_EXTENSIONS.includes(extension)) {
49
- return fam_table_type_enum_1.FAM_Table.codebase;
50
- }
51
- if (FAM_FileRouting_Util.isConfigFile(fileName)) {
52
- return fam_table_type_enum_1.FAM_Table.codebase;
64
+ if (FAM_FileRouting_Util.BINARY_EXTENSIONS.has(extension)) {
65
+ return 'skip';
53
66
  }
54
- return 'skip';
67
+ // Minden egyéb (ismert kód, ismeretlen-de-szöveges, extension-nélküli LICENSE/Dockerfile/git-hook) →
68
+ // codebase. A bináris-tartalmú álcázott fájlt a scanner null-byte tartalom-ellenőrzése szűri.
69
+ return fam_table_type_enum_1.FAM_Table.codebase;
55
70
  }
56
71
  /**
57
- * Config-fájl-e a fájlnév (dsgn-004 §2): extension-nélküli ismert config (`Dockerfile`) VAGY
58
- * a `*.config.*` / `tsconfig*.json` / `package.json` / `.eslintrc*` / `.prettierrc*` minta.
72
+ * Generált / zaj fájl-e (FAM-REV-064): a lock-fájlok (pnpm-lock.yaml / package-lock.json / yarn.lock /
73
+ * `*.lock`), a minifikált bundle-k (`.min.js`/`.min.css`) ezek hatalmasak + generáltak, NEM info-
74
+ * hordozó forrás, ezért `skip` (NEM vesztünk valódi információt; a forrás `.ts`/`.scss` megvan).
59
75
  */
60
- static isConfigFile(fileName) {
61
- if (FAM_FileRouting_Util.CONFIG_FILENAMES.includes(fileName)) {
62
- return true;
63
- }
64
- if (fileName === 'package.json' || fileName === 'package-lock.json') {
65
- return true;
66
- }
67
- if (/^tsconfig.*\.json$/.test(fileName)) {
76
+ static isGeneratedNoise(fileName) {
77
+ if (fileName === 'pnpm-lock.yaml' || fileName === 'package-lock.json' || fileName === 'yarn.lock'
78
+ || fileName === 'bun.lockb' || fileName === 'composer.lock') {
68
79
  return true;
69
80
  }
70
- if (/\.config\./.test(fileName)) {
81
+ if (fileName.endsWith('.lock')) {
71
82
  return true;
72
83
  }
73
- if (/^\.eslintrc/.test(fileName) || /^\.prettierrc/.test(fileName)) {
84
+ if (/\.min\.(js|css|mjs)$/.test(fileName)) {
74
85
  return true;
75
86
  }
76
87
  return false;
77
88
  }
78
89
  /**
79
- * A chunker-stratégia kulcsa egy fájlhoz (SP-4.2 dispatch, dsgn-004 §3). `ts` a TS-szemantikus
80
- * (.ts/.tsx/.mts/.js/.jsx), `md` a markdown-header-aware (.md/.mdx), `generic` minden más
81
- * `documents`/`codebase` fájlra (config/JSON/HTML-markup/.txt/.rst). A `route` előfeltétel:
82
- * `skip` fájlra NEM hívandó.
90
+ * A chunker-stratégia kulcsa egy fájlhoz (SP-4.2 dispatch, dsgn-004 §3). `ts` a TS/JS-szemantikus,
91
+ * `md` a markdown-header-aware (`.md`/`.mdx`/`.markdown`/`.mdc`), `generic` minden más `documents`/
92
+ * `codebase` fájlra (config/JSON/HTML/`.txt`/`.sh`/`.ps1`/`.xml`/ismeretlen-szöveg). A `route`
93
+ * előfeltétel: `skip` fájlra NEM hívandó.
83
94
  */
84
95
  static chunkStrategyFor(relativePath) {
85
96
  const extension = path.extname(path.basename(relativePath).toLowerCase());
@@ -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;
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAM_ProjectIdentity_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_ProjectIdentity_Util` — egy scan-gyökér **projekt-identitásának** felismerése (git + `package.json`).
9
+ * A scan ebből (a) egy kereshető `knowledge` „project-identity" entry-t készít (név/verzió/leírás/függőségek/
10
+ * git-repo), és (b) reference-aliasokat (a package-név + rövid alakok → a projekt scope-ja). A parse-rész PURE
11
+ * (önállóan tesztelhető), a `detect` az IO-wrapper (a `.git`/`package.json` olvasása — opt-in: hiányzó fájl no-op).
12
+ */
13
+ class FAM_ProjectIdentity_Util {
14
+ /** A generált identity-entry `kind`-ja. */
15
+ static KIND = 'project-identity';
16
+ /** A `package.json`-ból kinyert mezők (PURE — érvénytelen JSON → üres). */
17
+ static parsePackageJson(content) {
18
+ try {
19
+ const parsed = JSON.parse(content);
20
+ const deps = [
21
+ ...Object.keys(parsed.dependencies ?? {}),
22
+ ...Object.keys(parsed.peerDependencies ?? {}),
23
+ ];
24
+ return {
25
+ packageName: typeof parsed.name === 'string' ? parsed.name : undefined,
26
+ version: typeof parsed.version === 'string' ? parsed.version : undefined,
27
+ description: typeof parsed.description === 'string' ? parsed.description : undefined,
28
+ dependencies: Array.from(new Set(deps)).sort(),
29
+ };
30
+ }
31
+ catch {
32
+ return { dependencies: [] };
33
+ }
34
+ }
35
+ /** A git `origin` remote URL-je a `.git/config` tartalmából (PURE; nincs origin → undefined). */
36
+ static parseGitRemote(gitConfigContent) {
37
+ const lines = gitConfigContent.split(/\r?\n/);
38
+ let inOrigin = false;
39
+ for (const rawLine of lines) {
40
+ const line = rawLine.trim();
41
+ if (line.startsWith('[remote ')) {
42
+ inOrigin = line.includes('"origin"');
43
+ continue;
44
+ }
45
+ if (inOrigin && line.startsWith('url')) {
46
+ const eq = line.indexOf('=');
47
+ if (eq >= 0) {
48
+ return line.slice(eq + 1).trim();
49
+ }
50
+ }
51
+ }
52
+ return undefined;
53
+ }
54
+ /** Egy git-remote-URL → `org/repo` rövid alak (`.git` levágva; SSH/HTTPS egyaránt; PURE). Hiba → undefined. */
55
+ static shortRepo(remoteUrl) {
56
+ if (!remoteUrl) {
57
+ return undefined;
58
+ }
59
+ // git@host:org/repo.git VAGY https://host/org/repo.git
60
+ const afterHost = remoteUrl.includes(':') && !remoteUrl.startsWith('http')
61
+ ? remoteUrl.slice(remoteUrl.lastIndexOf(':') + 1)
62
+ : remoteUrl.replace(/^https?:\/\/[^/]+\//, '');
63
+ const cleaned = afterHost.replace(/\.git$/, '').replace(/^\/+/, '');
64
+ const segments = cleaned.split('/').filter(Boolean);
65
+ return segments.length >= 2 ? segments.slice(-2).join('/') : (segments[0] || undefined);
66
+ }
67
+ /** A projekt-identitás felismerése egy scan-gyökérből (IO: `package.json` + `.git/config`). Hiányzó → részleges. */
68
+ static detect(rootAbsolutePath) {
69
+ const identity = { isGit: false, dependencies: [] };
70
+ try {
71
+ const pkgPath = path.join(rootAbsolutePath, 'package.json');
72
+ if (fs.existsSync(pkgPath)) {
73
+ const parsed = FAM_ProjectIdentity_Util.parsePackageJson(fs.readFileSync(pkgPath, 'utf-8'));
74
+ identity.packageName = parsed.packageName;
75
+ identity.version = parsed.version;
76
+ identity.description = parsed.description;
77
+ identity.dependencies = parsed.dependencies;
78
+ }
79
+ const gitDir = path.join(rootAbsolutePath, '.git');
80
+ if (fs.existsSync(gitDir)) {
81
+ identity.isGit = true;
82
+ const configPath = path.join(gitDir, 'config');
83
+ if (fs.existsSync(configPath)) {
84
+ identity.gitRemote = FAM_ProjectIdentity_Util.parseGitRemote(fs.readFileSync(configPath, 'utf-8'));
85
+ identity.gitRepo = FAM_ProjectIdentity_Util.shortRepo(identity.gitRemote);
86
+ }
87
+ }
88
+ }
89
+ catch {
90
+ // best-effort: az identitás-olvasás hibája NEM buktatja a scant (a részleges identity visszaadva).
91
+ }
92
+ return identity;
93
+ }
94
+ /** Van-e bármi érdemi identitás (package.json VAGY git) — különben nincs értelme entry-t generálni. */
95
+ static hasIdentity(identity) {
96
+ return Boolean(identity.packageName || identity.isGit);
97
+ }
98
+ /** A kereshető `knowledge` „project-identity" entry szövege (PURE). */
99
+ static buildContent(identity, scopeLabel, root) {
100
+ const lines = [];
101
+ lines.push(`Projekt-identitás — ${scopeLabel}`);
102
+ lines.push(`Gyökér: ${root}`);
103
+ if (identity.packageName) {
104
+ lines.push(`Csomag: ${identity.packageName}${identity.version ? ` v${identity.version}` : ''}`);
105
+ }
106
+ lines.push(`Git-projekt: ${identity.isGit ? 'igen' : 'nem'}${identity.gitRepo ? ` (repo: ${identity.gitRepo})` : ''}`);
107
+ if (identity.description) {
108
+ lines.push(`Leírás: ${identity.description}`);
109
+ }
110
+ if (identity.dependencies.length) {
111
+ lines.push(`Függőségek (${identity.dependencies.length}): ${identity.dependencies.join(', ')}`);
112
+ }
113
+ return lines.join('\n');
114
+ }
115
+ /** A reference-aliasok (a package-név + rövid alakok + repo + dir-név → a projekt scope-ja). Dedup, üresek nélkül (PURE). */
116
+ static buildAliases(identity, dirName, scopeCanonical) {
117
+ const aliases = [dirName, scopeCanonical];
118
+ if (identity.packageName) {
119
+ aliases.push(identity.packageName);
120
+ const bare = identity.packageName.replace(/^@[^/]+\//, ''); // @futdevpro/fsm-dynamo → fsm-dynamo
121
+ aliases.push(bare);
122
+ const firstSegment = bare.split('-')[0]; // fsm-dynamo → fsm
123
+ if (firstSegment && firstSegment !== bare) {
124
+ aliases.push(firstSegment);
125
+ }
126
+ }
127
+ if (identity.gitRepo) {
128
+ aliases.push(identity.gitRepo);
129
+ aliases.push(identity.gitRepo.split('/').pop() ?? '');
130
+ }
131
+ return Array.from(new Set(aliases.map((alias) => alias.trim()).filter(Boolean)));
132
+ }
133
+ }
134
+ exports.FAM_ProjectIdentity_Util = FAM_ProjectIdentity_Util;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAM_ScanProgress_Util = void 0;
4
+ /**
5
+ * `FAM_ScanProgress_Util` (descriptive user feedback) — a scan **pre-becslés** + **futás-közbeni progress** PURE
6
+ * (DB-/provider-mentes) magja. A nagy, workspace-szintű scanhez: a hívó ELŐRE lássa a becsült időt, és FUTÁS
7
+ * közben kövesse a haladást (X/N fájl, %, chunk, verdikt-bontás, eltelt + ETA). Statikus util — a control-service
8
+ * a delta-számokból + az óra-időből táplálja; a formázás/ETA-matek itt izoláltan tesztelhető.
9
+ */
10
+ class FAM_ScanProgress_Util {
11
+ /**
12
+ * Pre-scan idő-becslés az embeddelendő chunk-számból + a per-chunk-ms-ből. `estimatedSeconds = chunks×ms/1000`.
13
+ * Durva (a tényleges sebesség gép-/modell-függő); a futó scan ETA-ja pontosítja.
14
+ */
15
+ static estimate(set) {
16
+ const estimatedSeconds = Math.round((set.chunksToEmbed * set.msPerChunk) / 1000);
17
+ return {
18
+ chunksToEmbed: set.chunksToEmbed,
19
+ msPerChunk: set.msPerChunk,
20
+ estimatedSeconds: estimatedSeconds,
21
+ note: `~${FAM_ScanProgress_Util.formatDuration(estimatedSeconds)} embeddelés (${set.chunksToEmbed} chunk × `
22
+ + `${set.msPerChunk}ms — DURVA becslés, gép-/modell-függő; a futó scan ETA-ja pontosítja)`,
23
+ };
24
+ }
25
+ /** Másodperc → ember-olvasható (`45s` / `3m 20s` / `1h 5m`). Negatív/0 → `0s`. */
26
+ static formatDuration(totalSeconds) {
27
+ const s = Math.max(0, Math.round(totalSeconds));
28
+ if (s < 60) {
29
+ return `${s}s`;
30
+ }
31
+ const minutes = Math.floor(s / 60);
32
+ const remSec = s % 60;
33
+ if (minutes < 60) {
34
+ return remSec ? `${minutes}m ${remSec}s` : `${minutes}m`;
35
+ }
36
+ const hours = Math.floor(minutes / 60);
37
+ const remMin = minutes % 60;
38
+ return remMin ? `${hours}h ${remMin}m` : `${hours}h`;
39
+ }
40
+ /**
41
+ * Egy futás-közbeni progress-sor: `processed/total` fájl + %, chunk-szám, verdikt-bontás, eltelt idő + ETA.
42
+ * Az ETA a MÉRT sebességből (`processed / elapsed`) → a hátralévő fájlok ideje (csak ha van haladás + maradék).
43
+ * `[famIngest]` prefix → az activity-monitor allowlistjén keresztül megjelenik a konzol-ablakban.
44
+ */
45
+ static progressLine(set) {
46
+ const pct = set.total ? Math.round((set.processed / set.total) * 100) : 100;
47
+ const rate = set.processed / Math.max(set.elapsedSeconds, 0.001);
48
+ const remaining = set.total - set.processed;
49
+ const eta = rate > 0 && remaining > 0
50
+ ? `, ETA ~${FAM_ScanProgress_Util.formatDuration(remaining / rate)}`
51
+ : '';
52
+ return `[famIngest] feldolgozás ${set.processed}/${set.total} fájl (${pct}%), ${set.chunkCount} chunk, `
53
+ + `${set.verdicts.new} új / ${set.verdicts.modified} mód / ${set.verdicts.equal} vált., `
54
+ + `eltelt ${FAM_ScanProgress_Util.formatDuration(set.elapsedSeconds)}${eta}`;
55
+ }
56
+ }
57
+ exports.FAM_ScanProgress_Util = FAM_ScanProgress_Util;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAM_ScanSummary_Util = void 0;
4
+ /**
5
+ * `FAM_ScanSummary_Util` (file-rendszer összefoglaló) — a scan által GENERÁLT, kereshető „mi van a projektben"
6
+ * összefoglaló PURE építője. A scan után egy `knowledge`-entry készül a mappastruktúrából (mappák + fájlok
7
+ * felsorolása), hogy az agent lekérdezhesse a projekt/mappa tartalmát (NEM kell végigscannelnie). Statikus util.
8
+ *
9
+ * **Méret-korlát:** a per-mappa fájllista a `MAX_FILES_PER_DIR`-nál levágódik (`+N további`), a teljes szöveg a
10
+ * `MAX_CHARS`-nál — így az összefoglaló egyetlen, embeddelhető chunk marad nagy fáknál is (minden MAPPA így is felsorolt).
11
+ */
12
+ class FAM_ScanSummary_Util {
13
+ /** A generált összefoglaló-entry `kind`-ja. */
14
+ static KIND = 'fs-summary';
15
+ /** A generált összefoglaló-entry `source.type`-ja (a reconciliation `scan`-t szűr → ezt nem érinti). */
16
+ static SOURCE_TYPE = 'scan-summary';
17
+ /** Per-mappa felsorolt fájlok plafonja (felette `+N további`). */
18
+ static MAX_FILES_PER_DIR = 80;
19
+ /** A teljes összefoglaló-szöveg plafonja (egyetlen embeddelhető chunk). */
20
+ static MAX_CHARS = 40000;
21
+ /**
22
+ * A mappastruktúra-összefoglaló szövege: fejléc (scope / root / fájl-számok) + a mappánként csoportosított,
23
+ * rendezett fájllista. A relatív utak `/`-normalizáltak; a gyökér-szintű fájlok a `(gyökér)` alatt.
24
+ */
25
+ static build(set) {
26
+ const byDir = new Map();
27
+ for (const rawPath of set.relativePaths) {
28
+ const normalized = rawPath.replace(/\\/g, '/');
29
+ const slashIndex = normalized.lastIndexOf('/');
30
+ const dir = slashIndex >= 0 ? normalized.slice(0, slashIndex) : '';
31
+ const name = slashIndex >= 0 ? normalized.slice(slashIndex + 1) : normalized;
32
+ if (!byDir.has(dir)) {
33
+ byDir.set(dir, []);
34
+ }
35
+ byDir.get(dir)?.push(name);
36
+ }
37
+ const lines = [];
38
+ lines.push(`File-rendszer összefoglaló — ${set.scopeLabel}`);
39
+ lines.push(`Gyökér: ${set.root}`);
40
+ lines.push(`${set.relativePaths.length} fájl ingestelve (${set.tables.join(', ') || '—'}), ${set.skippedCount} kihagyva.`);
41
+ lines.push('');
42
+ lines.push(`Mappák (${byDir.size}) + fájlok:`);
43
+ for (const dir of Array.from(byDir.keys()).sort()) {
44
+ const files = (byDir.get(dir) ?? []).sort();
45
+ const label = dir === '' ? '(gyökér)' : `${dir}/`;
46
+ const shown = files.length > FAM_ScanSummary_Util.MAX_FILES_PER_DIR
47
+ ? files.slice(0, FAM_ScanSummary_Util.MAX_FILES_PER_DIR)
48
+ : files;
49
+ const more = files.length > FAM_ScanSummary_Util.MAX_FILES_PER_DIR
50
+ ? ` (+${files.length - FAM_ScanSummary_Util.MAX_FILES_PER_DIR} további)`
51
+ : '';
52
+ lines.push(` ${label} — ${files.length} fájl: ${shown.join(', ')}${more}`);
53
+ }
54
+ const content = lines.join('\n');
55
+ return content.length > FAM_ScanSummary_Util.MAX_CHARS
56
+ ? `${content.slice(0, FAM_ScanSummary_Util.MAX_CHARS)}\n… (összefoglaló levágva a méret-plafonnál)`
57
+ : content;
58
+ }
59
+ }
60
+ exports.FAM_ScanSummary_Util = FAM_ScanSummary_Util;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAM_ScanWeight_Util = void 0;
4
+ const fam_glob_match_util_1 = require("./fam-glob-match.util");
5
+ /**
6
+ * `FAM_ScanWeight_Util` (FAM-REV-055) — a scan-idejű **súly-kiemelés** pure magja. A hívó (agent) scankor
7
+ * megadhat `weights` szabályokat (glob → súly), hogy a FONTOSABB fájlok chunkjai NAGYOBB retrieval-súlyt
8
+ * kapjanak (a `finalScore = score × weight` → előrébb a rangsorban). **First-match-wins:** a `weights` sorrend
9
+ * SZÁMÍT (az első illeszkedő szabály nyer). Nincs match → `undefined` (→ a tár model-default súlya marad).
10
+ */
11
+ class FAM_ScanWeight_Util {
12
+ /**
13
+ * Az adott (projekt-relatív) fájl súlya a `weights` szabályokból: az ELSŐ illeszkedő minta súlya, vagy
14
+ * `undefined` (nincs szabály / nincs match). Az illesztés a `FAM_GlobMatch_Util` (a scan ignore/include-pal
15
+ * azonos szemantika). A negatív / nem-véges súlyt kihagyja (defenzív — a 0/negatív weight retrieval-kizárást
16
+ * jelentene; ha szándékos, a `write`-on át explicit állítható).
17
+ */
18
+ static resolve(relativePath, weights) {
19
+ if (!weights || !weights.length) {
20
+ return undefined;
21
+ }
22
+ for (const rule of weights) {
23
+ if (typeof rule.weight !== 'number' || !Number.isFinite(rule.weight) || rule.weight <= 0) {
24
+ continue;
25
+ }
26
+ if (fam_glob_match_util_1.FAM_GlobMatch_Util.matches(relativePath, rule.pattern)) {
27
+ return rule.weight;
28
+ }
29
+ }
30
+ return undefined;
31
+ }
32
+ /** Teszt-/spec-fájl-e (`*.spec.*` / `*.test.*` a TS/JS-kiterjesztéseken). FAM-REV-058. */
33
+ static isTestFile(relativePath) {
34
+ return /\.(spec|test)\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/i.test(relativePath);
35
+ }
36
+ /**
37
+ * A fájl EFFEKTÍV súlya a DEFAULT teszt-súllyal (FAM-REV-058): az explicit scan-`weights` match NYER; különben
38
+ * egy teszt-/spec-fájl a `testFileWeight`-et kapja (a próza-nehéz specek különben az implementáció fölé
39
+ * rangsorolnak); különben `undefined` (→ a tár model-default súlya). A `testFileWeight===1` (nincs csökkentés)
40
+ * vagy hiánya → nincs default-súly.
41
+ */
42
+ static resolveWithDefault(relativePath, weights, testFileWeight) {
43
+ const explicit = FAM_ScanWeight_Util.resolve(relativePath, weights);
44
+ if (explicit !== undefined) {
45
+ return explicit;
46
+ }
47
+ if (typeof testFileWeight === 'number' && testFileWeight !== 1 && FAM_ScanWeight_Util.isTestFile(relativePath)) {
48
+ return testFileWeight;
49
+ }
50
+ return undefined;
51
+ }
52
+ }
53
+ exports.FAM_ScanWeight_Util = FAM_ScanWeight_Util;