graph-indexer 1.1.0 → 2.0.0

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 (59) hide show
  1. package/CHANGELOG.md +412 -0
  2. package/README.md +228 -350
  3. package/SECURITY.md +68 -0
  4. package/TRADEMARK.md +31 -0
  5. package/cli-ui.mjs +78 -0
  6. package/config.mjs +269 -0
  7. package/daemon-ctl.mjs +175 -0
  8. package/daemon-lock.mjs +69 -0
  9. package/embedders/python/mlx_embed_server.py +61 -0
  10. package/embedders/python/requirements-mlx.txt +5 -0
  11. package/embedders/setup-mlx.mjs +110 -0
  12. package/embeddings.mjs +489 -0
  13. package/engine/binary.mjs +399 -0
  14. package/{core-engine.mjs → engine/memory.mjs} +359 -249
  15. package/engine/sqlite.mjs +863 -0
  16. package/enrichment.mjs +426 -0
  17. package/git-signals.mjs +248 -0
  18. package/indexer.mjs +216 -77
  19. package/init.mjs +1604 -170
  20. package/layout.mjs +123 -0
  21. package/mcp/format.mjs +61 -0
  22. package/mcp/tools.mjs +1157 -0
  23. package/mcp/topology.mjs +551 -0
  24. package/mcp-server.mjs +118 -573
  25. package/package.json +56 -16
  26. package/parse/extractor.mjs +617 -0
  27. package/parse/imports.mjs +365 -0
  28. package/parse/languages.mjs +89 -0
  29. package/parse/metadata.mjs +655 -0
  30. package/parse/routes.mjs +327 -0
  31. package/prompts/CORE.md +86 -0
  32. package/prompts/DOMAIN_TEMPLATE.md +46 -0
  33. package/prompts/INTEGRATION.md +123 -0
  34. package/prompts/frameworks/ANDROID.md +17 -0
  35. package/prompts/frameworks/ASPNET_CORE.md +19 -0
  36. package/prompts/frameworks/FASTAPI_DJANGO.md +19 -0
  37. package/prompts/frameworks/LARAVEL_SYMFONY.md +22 -0
  38. package/prompts/frameworks/NODE_EXPRESS_NESTJS.md +18 -0
  39. package/prompts/frameworks/RAILS.md +20 -0
  40. package/prompts/frameworks/REACT.md +20 -0
  41. package/prompts/frameworks/SPRING_BOOT.md +19 -0
  42. package/prompts/languages/BASH.md +22 -0
  43. package/prompts/languages/C.md +21 -0
  44. package/prompts/languages/CSHARP.md +24 -0
  45. package/prompts/languages/CSS_SCSS.md +22 -0
  46. package/prompts/languages/GO.md +23 -0
  47. package/prompts/languages/JAVA.md +23 -0
  48. package/prompts/languages/JAVASCRIPT_TYPESCRIPT.md +24 -0
  49. package/prompts/languages/KOTLIN.md +24 -0
  50. package/prompts/languages/PHP.md +23 -0
  51. package/prompts/languages/PYTHON.md +23 -0
  52. package/prompts/languages/RUBY.md +23 -0
  53. package/prompts/languages/RUST.md +21 -0
  54. package/prompts/languages/SWIFT.md +21 -0
  55. package/search-core.mjs +627 -0
  56. package/storage.mjs +68 -0
  57. package/watch-daemon.mjs +204 -69
  58. package/PROMPT.md +0 -143
  59. package/parser-utils.mjs +0 -861
package/layout.mjs ADDED
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @file layout.mjs
3
+ * @description Single source of truth for graph-indexer's on-disk layout. All
4
+ * machine-generated runtime state lives in one tidy `.graph-indexer/`
5
+ * directory at the project root (index, vectors, SQLite db, enrichment
6
+ * cache, daemon pid/log, resolved config) instead of littering the
7
+ * root. Earlier versions wrote these artifacts straight into the root;
8
+ * `migrateLegacyLayout` relocates them transparently on the next run.
9
+ *
10
+ * Files that other tools must discover at conventional paths — the
11
+ * agent prompt suite (CLAUDE.md, GRAPH_INDEXER_PROMPT.md, Cursor
12
+ * rules) and IDE MCP configs (.vscode/mcp.json, .cursor/mcp.json, …) —
13
+ * intentionally stay where those tools expect them.
14
+ * @author MaquinaTech <https://github.com/MaquinaTech>
15
+ * @copyright (c) 2026 MaquinaTech. All rights reserved.
16
+ * @license MIT
17
+ */
18
+ import fs from 'fs';
19
+ import path from 'path';
20
+
21
+ export const DATA_DIR_NAME = '.graph-indexer';
22
+
23
+ export const CONFIG_FILE_NAME = 'config.json';
24
+
25
+ export function dataDir(root) {
26
+ return path.join(root, DATA_DIR_NAME);
27
+ }
28
+
29
+ export function ensureDataDir(root) {
30
+ const dir = dataDir(root);
31
+ fs.mkdirSync(dir, { recursive: true });
32
+ return dir;
33
+ }
34
+
35
+ /**
36
+ * All generated artifact paths for a project root, derived from one stem so the
37
+ * indexer, daemon, MCP server and test harness can never drift apart.
38
+ * @returns {{dataDir,indexPath,embeddingPath,sqlitePath,enrichmentCachePath,pidFile,logFile,configPath}}
39
+ */
40
+ export function artifactPaths(root) {
41
+ const dir = dataDir(root);
42
+ return {
43
+ dataDir: dir,
44
+ indexPath: path.join(dir, 'code-index.json'),
45
+ embeddingPath: path.join(dir, 'code-index.embeddings.bin'),
46
+ sqlitePath: path.join(dir, 'code-index.db'),
47
+ enrichmentCachePath: path.join(dir, 'code-index.enrichment.json'),
48
+ gitSignalsPath: path.join(dir, 'code-index.git.json'),
49
+ pidFile: path.join(dir, 'daemon.pid'),
50
+ logFile: path.join(dir, 'daemon.log'),
51
+ configPath: path.join(dir, CONFIG_FILE_NAME),
52
+ };
53
+ }
54
+
55
+ // ─── Legacy → current migration ─────────────────────────────────────────────────
56
+
57
+ // Regenerable runtime artifacts that lived at the project root before v1.4.
58
+ // Each [legacy-root-name, new-name-inside-data-dir]. When both copies exist the
59
+ // stale root one is safe to drop — it is a derived cache, not user data.
60
+ const LEGACY_ARTIFACTS = [
61
+ ['code-index.json', 'code-index.json'],
62
+ ['code-index.json.tmp', 'code-index.json.tmp'],
63
+ ['code-index.embeddings.bin', 'code-index.embeddings.bin'],
64
+ ['code-index.embeddings.bin.tmp', 'code-index.embeddings.bin.tmp'],
65
+ ['code-index.db', 'code-index.db'],
66
+ ['code-index.db-wal', 'code-index.db-wal'],
67
+ ['code-index.db-shm', 'code-index.db-shm'],
68
+ ['code-index.enrichment.json', 'code-index.enrichment.json'],
69
+ ['.idx-daemon.pid', 'daemon.pid'],
70
+ ['.idx-daemon.log', 'daemon.log'],
71
+ ];
72
+
73
+ function relocate(src, dst) {
74
+ try { fs.renameSync(src, dst); return true; }
75
+ catch {
76
+ try { fs.copyFileSync(src, dst); fs.unlinkSync(src); return true; }
77
+ catch { return false; }
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Relocate pre-v1.4 root artifacts into `.graph-indexer/`. Idempotent and
83
+ * non-destructive to live data: an artifact is only *moved* when the destination
84
+ * is absent; when a fresh copy already exists in the data dir, the orphaned root
85
+ * copy (a regenerable cache) is removed to declutter. The user-editable config
86
+ * (`.graph-indexer.json`) is only ever moved when no new config exists yet —
87
+ * never overwritten or deleted out from under the user.
88
+ *
89
+ * @returns {{moved:Array<{from:string,to:string}>, removed:string[], stoppedDaemon:boolean}}
90
+ */
91
+ export function migrateLegacyLayout(root) {
92
+ const moved = [];
93
+ const removed = [];
94
+ const dir = dataDir(root);
95
+
96
+ for (const [legacyName, newName] of LEGACY_ARTIFACTS) {
97
+ const src = path.join(root, legacyName);
98
+ if (!fs.existsSync(src)) continue;
99
+ const dst = path.join(dir, newName);
100
+ if (fs.existsSync(dst)) {
101
+ try { fs.unlinkSync(src); removed.push(legacyName); } catch { /* keep on failure */ }
102
+ } else {
103
+ ensureDataDir(root);
104
+ if (relocate(src, dst)) moved.push({ from: legacyName, to: path.join(DATA_DIR_NAME, newName) });
105
+ }
106
+ }
107
+
108
+ const legacyCfg = path.join(root, '.graph-indexer.json');
109
+ const newCfg = path.join(dir, CONFIG_FILE_NAME);
110
+ if (fs.existsSync(legacyCfg) && !fs.existsSync(newCfg)) {
111
+ ensureDataDir(root);
112
+ if (relocate(legacyCfg, newCfg)) {
113
+ moved.push({ from: '.graph-indexer.json', to: path.join(DATA_DIR_NAME, CONFIG_FILE_NAME) });
114
+ }
115
+ }
116
+
117
+ return { moved, removed, stoppedDaemon: false };
118
+ }
119
+
120
+ export function hasLegacyLayout(root) {
121
+ if (fs.existsSync(path.join(root, '.graph-indexer.json'))) return true;
122
+ return LEGACY_ARTIFACTS.some(([legacyName]) => fs.existsSync(path.join(root, legacyName)));
123
+ }
package/mcp/format.mjs ADDED
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @file mcp/format.mjs
3
+ * @description MCP rendering helpers: signature extraction, body pruning.
4
+ * @author MaquinaTech <https://github.com/MaquinaTech>
5
+ * @copyright (c) 2026 MaquinaTech. All rights reserved.
6
+ * @license MIT
7
+ */
8
+
9
+ // ─── Rendering helpers ──────────────────────────────────────────────────────────
10
+
11
+ export function extractSignatureLine(codeSnippet) {
12
+ if (!codeSnippet) return '';
13
+ const lines = codeSnippet.split('\n');
14
+ const sigLines = [];
15
+ for (let i = 0; i < Math.min(lines.length, 10); i++) {
16
+ sigLines.push(lines[i]);
17
+ const l = lines[i];
18
+ if (i > 0 && (l.trimEnd().endsWith('{') || l.includes('=>') || l.trimEnd().endsWith(':'))) break;
19
+ }
20
+ return sigLines.join('\n');
21
+ }
22
+
23
+ /**
24
+ * When no lexical token matches (e.g. the agent used a high-level description
25
+ * like "authentication bottleneck" that isn't in the code verbatim), fall back
26
+ * to preserving structural skeleton — control-flow lines and calls — rather than
27
+ * blindly truncating, so the caller still gets meaningful context.
28
+ */
29
+ export function pruneBodyByQuery(codeSnippet, queryTokens, maxLines = 40) {
30
+ if (!codeSnippet) return '';
31
+ const lines = codeSnippet.split('\n');
32
+ if (lines.length <= maxLines) return codeSnippet;
33
+
34
+ const querySet = new Set(queryTokens.filter(t => t.length >= 3).map(t => t.toLowerCase()));
35
+ if (querySet.size === 0) return lines.slice(0, maxLines).join('\n') + '\n// …';
36
+
37
+ const SIG_LINES = Math.min(5, lines.length);
38
+ const TAIL_LINES = Math.min(3, lines.length);
39
+ const sigBlock = lines.slice(0, SIG_LINES);
40
+ const tailBlock = lines.slice(Math.max(lines.length - TAIL_LINES, SIG_LINES));
41
+
42
+ const bodyLines = lines.slice(SIG_LINES, lines.length - TAIL_LINES);
43
+ const relevant = bodyLines.filter(line => {
44
+ const ll = line.toLowerCase();
45
+ if (/^\s*(return|throw|raise|yield)\b/.test(ll)) return true;
46
+ return [...querySet].some(token => ll.includes(token));
47
+ });
48
+
49
+ if (relevant.length === 0) {
50
+ const budget = Math.max(4, maxLines - SIG_LINES - TAIL_LINES);
51
+ const structural = bodyLines.filter(line => {
52
+ const ll = line.trimStart().toLowerCase();
53
+ if (/^(if |else |for |while |switch |try |catch |finally |return |throw |raise |yield |await )/.test(ll)) return true;
54
+ if (/[a-zA-Z_]\w*\s*\(/.test(line) && line.trim().length > 4) return true;
55
+ return false;
56
+ }).slice(0, budget);
57
+ if (structural.length > 0) return [...sigBlock, ...structural, ...tailBlock].join('\n');
58
+ return lines.slice(0, maxLines).join('\n') + '\n// …';
59
+ }
60
+ return [...sigBlock, ...relevant, ...tailBlock].join('\n');
61
+ }