graph-indexer 2.1.1 → 3.0.1

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 (131) hide show
  1. package/CHANGELOG.md +312 -1
  2. package/README.md +236 -312
  3. package/SECURITY.md +53 -58
  4. package/bin/graph-indexer.mjs +251 -0
  5. package/integrations/claude-code/.claude-plugin/plugin.json +9 -0
  6. package/integrations/claude-code/.mcp.json +8 -0
  7. package/integrations/claude-code/agents/code-structure.md +22 -0
  8. package/integrations/claude-code/hooks/gi-hook.mjs +103 -0
  9. package/integrations/claude-code/hooks/hooks.json +16 -0
  10. package/integrations/opencode/graph-indexer.js +129 -0
  11. package/package.json +32 -109
  12. package/src/cli/helper.mjs +67 -0
  13. package/src/cli/hook.mjs +366 -0
  14. package/src/cli/init.mjs +272 -0
  15. package/src/cli/resident.mjs +185 -0
  16. package/src/index/discover.mjs +128 -0
  17. package/src/index/fingerprint.mjs +30 -0
  18. package/src/index/graph.mjs +61 -0
  19. package/src/index/indexer.mjs +476 -0
  20. package/src/index/modules.mjs +332 -0
  21. package/src/index/resolver.mjs +961 -0
  22. package/src/mcp/render.mjs +60 -0
  23. package/src/mcp/server.mjs +147 -0
  24. package/src/mcp/tools.mjs +974 -0
  25. package/src/parse/arity.mjs +153 -0
  26. package/src/parse/extract.mjs +1038 -0
  27. package/src/parse/lang/csharp.mjs +106 -0
  28. package/src/parse/lang/ecma.mjs +438 -0
  29. package/src/parse/lang/go.mjs +194 -0
  30. package/src/parse/lang/jvm.mjs +118 -0
  31. package/src/parse/lang/kotlin.mjs +97 -0
  32. package/src/parse/lang/misc.mjs +115 -0
  33. package/src/parse/lang/native.mjs +126 -0
  34. package/src/parse/lang/python.mjs +153 -0
  35. package/src/parse/lang/rust.mjs +157 -0
  36. package/src/parse/lang/scripting.mjs +219 -0
  37. package/src/parse/languages.mjs +48 -0
  38. package/src/parse/runtime.mjs +56 -0
  39. package/src/query/check.mjs +345 -0
  40. package/src/query/facts.mjs +323 -0
  41. package/src/query/intel.mjs +683 -0
  42. package/src/query/read.mjs +81 -0
  43. package/src/query/testcmd.mjs +130 -0
  44. package/src/query/textsearch.mjs +178 -0
  45. package/src/search/search.mjs +210 -0
  46. package/src/search/tokenize.mjs +188 -0
  47. package/src/store/db.mjs +169 -0
  48. package/src/util/git.mjs +30 -0
  49. package/src/util/paths.mjs +63 -0
  50. package/src/util/regex.mjs +2 -0
  51. package/vendor/grammars/bash.wasm.gz +0 -0
  52. package/vendor/grammars/c.wasm.gz +0 -0
  53. package/vendor/grammars/c_sharp.wasm.gz +0 -0
  54. package/vendor/grammars/cpp.wasm.gz +0 -0
  55. package/vendor/grammars/css.wasm.gz +0 -0
  56. package/vendor/grammars/go.wasm.gz +0 -0
  57. package/vendor/grammars/java.wasm.gz +0 -0
  58. package/vendor/grammars/javascript.wasm.gz +0 -0
  59. package/vendor/grammars/kotlin.wasm.gz +0 -0
  60. package/vendor/grammars/manifest.json +148 -0
  61. package/vendor/grammars/php.wasm.gz +0 -0
  62. package/vendor/grammars/python.wasm.gz +0 -0
  63. package/vendor/grammars/ruby.wasm.gz +0 -0
  64. package/vendor/grammars/rust.wasm.gz +0 -0
  65. package/vendor/grammars/scala.wasm.gz +0 -0
  66. package/vendor/grammars/tsx.wasm.gz +0 -0
  67. package/vendor/grammars/typescript.wasm.gz +0 -0
  68. package/vendor/web-tree-sitter/web-tree-sitter.js +3980 -0
  69. package/vendor/web-tree-sitter/web-tree-sitter.wasm +0 -0
  70. package/cli-ui.mjs +0 -78
  71. package/colbert.mjs +0 -168
  72. package/config.mjs +0 -494
  73. package/daemon-ctl.mjs +0 -175
  74. package/daemon-lock.mjs +0 -69
  75. package/embedders/python/mlx_embed_server.py +0 -61
  76. package/embedders/python/requirements-mlx.txt +0 -5
  77. package/embedders/setup-mlx.mjs +0 -110
  78. package/embeddings.mjs +0 -571
  79. package/engine/binary.mjs +0 -437
  80. package/engine/memory.mjs +0 -1003
  81. package/engine/sqlite.mjs +0 -1136
  82. package/enrichment.mjs +0 -521
  83. package/git-signals.mjs +0 -248
  84. package/indexer.mjs +0 -546
  85. package/init.mjs +0 -1809
  86. package/layout.mjs +0 -123
  87. package/mcp/centrality.mjs +0 -117
  88. package/mcp/format.mjs +0 -61
  89. package/mcp/resolver.mjs +0 -116
  90. package/mcp/symbolgraph.mjs +0 -137
  91. package/mcp/taint.mjs +0 -225
  92. package/mcp/tools.mjs +0 -1582
  93. package/mcp/topology.mjs +0 -676
  94. package/mcp-server.mjs +0 -197
  95. package/parse/extractor.mjs +0 -628
  96. package/parse/imports.mjs +0 -365
  97. package/parse/interprocedural.mjs +0 -134
  98. package/parse/languages.mjs +0 -253
  99. package/parse/metadata.mjs +0 -701
  100. package/parse/routes.mjs +0 -327
  101. package/parse/scip.mjs +0 -391
  102. package/parse/taint-patterns.mjs +0 -119
  103. package/prompts/CORE.md +0 -91
  104. package/prompts/DOMAIN_TEMPLATE.md +0 -46
  105. package/prompts/INTEGRATION.md +0 -123
  106. package/prompts/frameworks/ANDROID.md +0 -17
  107. package/prompts/frameworks/ASPNET_CORE.md +0 -19
  108. package/prompts/frameworks/FASTAPI_DJANGO.md +0 -20
  109. package/prompts/frameworks/LARAVEL_SYMFONY.md +0 -22
  110. package/prompts/frameworks/NODE_EXPRESS_NESTJS.md +0 -19
  111. package/prompts/frameworks/RAILS.md +0 -20
  112. package/prompts/frameworks/REACT.md +0 -20
  113. package/prompts/frameworks/SPRING_BOOT.md +0 -20
  114. package/prompts/languages/BASH.md +0 -22
  115. package/prompts/languages/C.md +0 -21
  116. package/prompts/languages/CSHARP.md +0 -24
  117. package/prompts/languages/CSS_SCSS.md +0 -22
  118. package/prompts/languages/GO.md +0 -24
  119. package/prompts/languages/JAVA.md +0 -23
  120. package/prompts/languages/JAVASCRIPT_TYPESCRIPT.md +0 -24
  121. package/prompts/languages/KOTLIN.md +0 -24
  122. package/prompts/languages/PHP.md +0 -23
  123. package/prompts/languages/PYTHON.md +0 -23
  124. package/prompts/languages/RUBY.md +0 -23
  125. package/prompts/languages/RUST.md +0 -21
  126. package/prompts/languages/SWIFT.md +0 -21
  127. package/seal.mjs +0 -400
  128. package/search-core.mjs +0 -777
  129. package/search-sparse.mjs +0 -183
  130. package/storage.mjs +0 -73
  131. package/watch-daemon.mjs +0 -289
package/git-signals.mjs DELETED
@@ -1,248 +0,0 @@
1
- /**
2
- * @file git-signals.mjs
3
- * @description Lightweight, air-gapped git-history signals for ranking and
4
- * blast-radius hints. Reads ONLY the local repository's commit log
5
- * (no network, no remote, no code sent anywhere) and derives three
6
- * cheap per-file signals competitors mostly ignore:
7
- *
8
- * • churn — how often a file changes (commit count);
9
- * • recency — how recently it last changed (last-commit time);
10
- * • co-change — which files historically change *together* (the
11
- * "if you touch A you usually touch B" hint that
12
- * sharpens the refactoring/blast-radius story).
13
- *
14
- * Computed once at index time into a sidecar (code-index.git.json),
15
- * kept out of the storage contract and the ranking math: the
16
- * co-change hint is surfaced by the blast-radius tools, and the
17
- * recency/churn boost is applied opt-in in the tool layer (default
18
- * off), so the measured retrieval ranking is unchanged by default.
19
- * @author MaquinaTech <https://github.com/MaquinaTech>
20
- * @copyright (c) 2026 MaquinaTech. All rights reserved.
21
- * @license MIT
22
- */
23
- import { execFileSync } from 'child_process';
24
- import fs from 'fs';
25
- import path from 'path';
26
-
27
- // Record/field separators chosen to never collide with file paths.
28
- const REC = '\x01';
29
- const FS_ = '\x1f';
30
-
31
- /**
32
- * Collect git signals for a repository from its local commit log.
33
- *
34
- * Strictly local: shells out to `git log` on `repoRoot` and parses stdout. No
35
- * remote is contacted. Returns `null` (a no-op) when the directory is not a git
36
- * repo, git is unavailable, or there is no history — callers degrade silently.
37
- *
38
- * @param {string} repoRoot
39
- * @param {object} [opts]
40
- * @param {number} [opts.maxCommits=500] History depth (bounds cost + size).
41
- * @param {number} [opts.topCoChange=8] Co-changed files kept per file.
42
- * @param {number} [opts.maxFilesPerCommit=50] Skip sweeping commits (mass renames,
43
- * formatting) whose co-change is noise.
44
- * @returns {null | {generated:number, commits:number, maxChurn:number,
45
- * churn:Object<string,number>, lastCommit:Object<string,number>,
46
- * coChange:Object<string,Array<[string,number]>>}}
47
- */
48
- export function collectGitSignals(repoRoot, { maxCommits = 500, topCoChange = 8, maxFilesPerCommit = 50 } = {}) {
49
- // Resolve the actual repo root. If `repoRoot` is a SUBDIRECTORY of a larger
50
- // repo (e.g. indexing one package, or a fixture nested in this repo), a bare
51
- // `git -C <dir> log` walks UP and returns the PARENT repo's history with
52
- // parent-root-relative paths that never match this index's file paths. So we
53
- // find the toplevel, scope the log to this subtree, and strip the prefix so
54
- // emitted paths are relative to `repoRoot` (matching chunk.file_path).
55
- let prefix = '';
56
- try {
57
- const toplevel = execFileSync('git', ['-C', repoRoot, 'rev-parse', '--show-toplevel'],
58
- { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
59
- if (!toplevel) return null;
60
- // Compare REAL paths: git returns the physical toplevel, while repoRoot may
61
- // contain symlinks (e.g. macOS /var → /private/var), which would otherwise
62
- // produce a bogus prefix.
63
- const realTop = (() => { try { return fs.realpathSync(toplevel); } catch { return toplevel; } })();
64
- const realRoot = (() => { try { return fs.realpathSync(path.resolve(repoRoot)); } catch { return path.resolve(repoRoot); } })();
65
- const rel = path.relative(realTop, realRoot).replace(/\\/g, '/');
66
- prefix = rel && rel !== '.' ? rel : '';
67
- } catch {
68
- return null; // not a git repo / git missing — air-gapped no-op
69
- }
70
-
71
- let raw;
72
- try {
73
- const args = ['-C', repoRoot, 'log', '--no-merges', `-n${maxCommits}`,
74
- `--pretty=format:${REC}%H${FS_}%ct`, '--name-only'];
75
- if (prefix) args.push('--', '.'); // limit to this subtree (paths stay toplevel-relative)
76
- raw = execFileSync('git', args,
77
- { encoding: 'utf-8', maxBuffer: 128 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] });
78
- } catch {
79
- return null;
80
- }
81
- if (!raw || !raw.includes(REC)) return null;
82
- const stripPrefix = prefix ? prefix + '/' : '';
83
-
84
- const churn = Object.create(null);
85
- const lastCommit = Object.create(null);
86
- const pairCounts = new Map(); // file → Map<coFile, count>
87
- let commits = 0;
88
-
89
- const bump = (f, ts) => {
90
- churn[f] = (churn[f] || 0) + 1;
91
- if (ts > (lastCommit[f] || 0)) lastCommit[f] = ts;
92
- };
93
-
94
- for (const block of raw.split(REC)) {
95
- if (!block) continue;
96
- const nl = block.indexOf('\n');
97
- const header = nl === -1 ? block : block.slice(0, nl);
98
- const ts = parseInt(header.split(FS_)[1], 10);
99
- if (!Number.isFinite(ts)) continue;
100
- commits++;
101
-
102
- const files = [];
103
- if (nl !== -1) {
104
- for (const line of block.slice(nl + 1).split('\n')) {
105
- let f = line.trim();
106
- if (!f) continue;
107
- // git quotes paths with unusual chars: "src/é.ts" — unwrap.
108
- if (f.startsWith('"') && f.endsWith('"')) f = f.slice(1, -1);
109
- if (stripPrefix) {
110
- if (!f.startsWith(stripPrefix)) continue; // outside the indexed subtree
111
- f = f.slice(stripPrefix.length);
112
- }
113
- files.push(f);
114
- }
115
- }
116
- for (const f of files) bump(f, ts);
117
-
118
- // Co-change: only for focused commits — a 400-file sweep links nothing useful.
119
- if (files.length >= 2 && files.length <= maxFilesPerCommit) {
120
- for (let i = 0; i < files.length; i++) {
121
- const a = files[i];
122
- let row = pairCounts.get(a);
123
- if (!row) { row = new Map(); pairCounts.set(a, row); }
124
- for (let j = 0; j < files.length; j++) {
125
- if (i === j) continue;
126
- const b = files[j];
127
- row.set(b, (row.get(b) || 0) + 1);
128
- }
129
- }
130
- }
131
- }
132
-
133
- if (commits === 0) return null;
134
-
135
- // Stamp HEAD so clients can report "indexed at commit X" and detect drift
136
- // (the freshness contract). Best-effort — absence just omits the commit.
137
- let head = null;
138
- try {
139
- head = execFileSync('git', ['-C', repoRoot, 'rev-parse', '--short', 'HEAD'],
140
- { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() || null;
141
- } catch { /* detached/empty — no stamp */ }
142
-
143
- const coChange = Object.create(null);
144
- for (const [file, row] of pairCounts) {
145
- const top = [...row.entries()]
146
- .sort((x, y) => y[1] - x[1] || x[0].localeCompare(y[0]))
147
- .slice(0, topCoChange);
148
- if (top.length) coChange[file] = top;
149
- }
150
-
151
- let maxChurn = 1;
152
- for (const k in churn) if (churn[k] > maxChurn) maxChurn = churn[k];
153
-
154
- return { generated: Math.floor(Date.now() / 1000), head, commits, maxChurn, churn, lastCommit, coChange };
155
- }
156
-
157
- /** Persist signals next to the index (best-effort). */
158
- export function writeGitSignals(filePath, signals) {
159
- try { fs.writeFileSync(filePath, JSON.stringify(signals)); return true; }
160
- catch { return false; }
161
- }
162
-
163
- /** Load a signals sidecar, or null if absent/corrupt. */
164
- export function loadGitSignals(filePath) {
165
- try { return JSON.parse(fs.readFileSync(filePath, 'utf-8')); }
166
- catch { return null; }
167
- }
168
-
169
- /** Files that historically change together with `file`, strongest first. */
170
- export function coChangesFor(signals, file, limit = 5) {
171
- if (!signals || !signals.coChange) return [];
172
- const row = signals.coChange[file];
173
- if (!row) return [];
174
- return row.slice(0, limit).map(([f, count]) => ({ file: f, count }));
175
- }
176
-
177
- // ─── Index freshness (query-time drift detection) ────────────────────────────
178
- // The blast-radius tools are only trustworthy if the index reflects the working
179
- // tree. We surface a compact freshness signal on tool responses: index age, the
180
- // commit it was built at (stamped above), and whether the tree has drifted since.
181
- // The git read is cheap and cached briefly so a burst of tool calls costs one
182
- // `git status`; it degrades silently (null) outside a git repo.
183
-
184
- const _SRC_RE = /\.(js|jsx|mjs|cjs|ts|tsx|py|go|rs|java|kt|cs|rb|php|swift|c|h|cc|cpp|hpp|scss|css|sh|bash)$/i;
185
- let _gitStateCache = null; // { at, root, head, dirtyCount }
186
-
187
- /** Reset the cached working-tree state (tests). */
188
- export function _resetGitStateCache() { _gitStateCache = null; }
189
-
190
- /**
191
- * Cheap, cached working-tree state: current short HEAD + count of changed SOURCE
192
- * files (uncommitted). Cached `ttlMs` per root so a burst of tool calls shells out
193
- * at most once. `exec`/`nowMs` injectable for tests. Returns {head:null,dirtyCount:null}
194
- * outside a git repo.
195
- */
196
- export function currentGitState(repoRoot, { ttlMs = 5000, nowMs = Date.now(), exec = execFileSync } = {}) {
197
- if (_gitStateCache && _gitStateCache.root === repoRoot && (nowMs - _gitStateCache.at) < ttlMs) return _gitStateCache;
198
- let head = null, dirtyCount = null;
199
- try {
200
- head = exec('git', ['-C', repoRoot, 'rev-parse', '--short', 'HEAD'],
201
- { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() || null;
202
- const status = exec('git', ['-C', repoRoot, 'status', '--porcelain'],
203
- { encoding: 'utf-8', maxBuffer: 16 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] });
204
- dirtyCount = status.split('\n').filter(l => l.trim() && _SRC_RE.test(l)).length;
205
- } catch { /* not a git repo / git missing → unknown */ }
206
- _gitStateCache = { at: nowMs, root: repoRoot, head, dirtyCount };
207
- return _gitStateCache;
208
- }
209
-
210
- /**
211
- * Derive the freshness verdict from index age + the stamped index commit + the
212
- * current working-tree state + whether the watch daemon is live. Pure (no I/O) so
213
- * it is trivially testable.
214
- *
215
- * @returns {{ageSeconds:number|null, ageLabel:string, indexedCommit:string|null,
216
- * currentCommit:string|null, pendingChanges:number|null, commitMoved:boolean,
217
- * daemonRunning:boolean, stale:boolean, syncing:boolean}}
218
- */
219
- export function computeFreshness({ ageSeconds = null, indexedCommit = null, current = null, daemonRunning = false } = {}) {
220
- const ageLabel = ageSeconds == null ? 'unknown'
221
- : ageSeconds < 60 ? `${ageSeconds}s` : ageSeconds < 3600 ? `${Math.floor(ageSeconds / 60)}m` : `${Math.floor(ageSeconds / 3600)}h`;
222
- const currentCommit = current?.head ?? null;
223
- const pendingChanges = current?.dirtyCount ?? null;
224
- const commitMoved = Boolean(indexedCommit && currentCommit && indexedCommit !== currentCommit);
225
- const hasPending = (pendingChanges != null && pendingChanges > 0) || commitMoved;
226
- // A live daemon syncs changes incrementally, so pending work is "syncing", not
227
- // "stale". Only when no daemon is keeping the index fresh is drift truly stale.
228
- return {
229
- ageSeconds, ageLabel, indexedCommit, currentCommit, pendingChanges, commitMoved,
230
- daemonRunning, stale: hasPending && !daemonRunning, syncing: hasPending && daemonRunning,
231
- };
232
- }
233
-
234
- /**
235
- * A bounded 0..1 "this file is hot" score combining recency and churn, for the
236
- * OPT-IN ranking boost only. Returns 0 when there is no signal for the file, so a
237
- * disabled or signal-less repo never perturbs ranking.
238
- */
239
- export function gitBoostScore(signals, file, nowSec = Date.now() / 1000) {
240
- if (!signals) return 0;
241
- const churn = (signals.churn && signals.churn[file]) || 0;
242
- const last = (signals.lastCommit && signals.lastCommit[file]) || 0;
243
- if (!churn && !last) return 0;
244
- const ageDays = last ? Math.max(0, (nowSec - last) / 86400) : 3650;
245
- const recency = Math.exp(-ageDays / 90); // ~quarter half-life
246
- const churnScore = Math.log1p(churn) / Math.log1p(signals.maxChurn || 1); // 0..1
247
- return 0.5 * recency + 0.5 * churnScore;
248
- }