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.
- package/CHANGELOG.md +412 -0
- package/README.md +228 -350
- package/SECURITY.md +68 -0
- package/TRADEMARK.md +31 -0
- package/cli-ui.mjs +78 -0
- package/config.mjs +269 -0
- package/daemon-ctl.mjs +175 -0
- package/daemon-lock.mjs +69 -0
- package/embedders/python/mlx_embed_server.py +61 -0
- package/embedders/python/requirements-mlx.txt +5 -0
- package/embedders/setup-mlx.mjs +110 -0
- package/embeddings.mjs +489 -0
- package/engine/binary.mjs +399 -0
- package/{core-engine.mjs → engine/memory.mjs} +359 -249
- package/engine/sqlite.mjs +863 -0
- package/enrichment.mjs +426 -0
- package/git-signals.mjs +248 -0
- package/indexer.mjs +216 -77
- package/init.mjs +1604 -170
- package/layout.mjs +123 -0
- package/mcp/format.mjs +61 -0
- package/mcp/tools.mjs +1157 -0
- package/mcp/topology.mjs +551 -0
- package/mcp-server.mjs +118 -573
- package/package.json +56 -16
- package/parse/extractor.mjs +617 -0
- package/parse/imports.mjs +365 -0
- package/parse/languages.mjs +89 -0
- package/parse/metadata.mjs +655 -0
- package/parse/routes.mjs +327 -0
- package/prompts/CORE.md +86 -0
- package/prompts/DOMAIN_TEMPLATE.md +46 -0
- package/prompts/INTEGRATION.md +123 -0
- package/prompts/frameworks/ANDROID.md +17 -0
- package/prompts/frameworks/ASPNET_CORE.md +19 -0
- package/prompts/frameworks/FASTAPI_DJANGO.md +19 -0
- package/prompts/frameworks/LARAVEL_SYMFONY.md +22 -0
- package/prompts/frameworks/NODE_EXPRESS_NESTJS.md +18 -0
- package/prompts/frameworks/RAILS.md +20 -0
- package/prompts/frameworks/REACT.md +20 -0
- package/prompts/frameworks/SPRING_BOOT.md +19 -0
- package/prompts/languages/BASH.md +22 -0
- package/prompts/languages/C.md +21 -0
- package/prompts/languages/CSHARP.md +24 -0
- package/prompts/languages/CSS_SCSS.md +22 -0
- package/prompts/languages/GO.md +23 -0
- package/prompts/languages/JAVA.md +23 -0
- package/prompts/languages/JAVASCRIPT_TYPESCRIPT.md +24 -0
- package/prompts/languages/KOTLIN.md +24 -0
- package/prompts/languages/PHP.md +23 -0
- package/prompts/languages/PYTHON.md +23 -0
- package/prompts/languages/RUBY.md +23 -0
- package/prompts/languages/RUST.md +21 -0
- package/prompts/languages/SWIFT.md +21 -0
- package/search-core.mjs +627 -0
- package/storage.mjs +68 -0
- package/watch-daemon.mjs +204 -69
- package/PROMPT.md +0 -143
- 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
|
+
}
|