@openez-graph/cli 1.2.0 → 1.3.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/README.md +12 -10
- package/dist/CHANGELOG.md +15 -0
- package/dist/cli.cjs +360 -54
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -9,12 +9,12 @@ OpenEZ Graph indexes your codebase into a local SQLite database, builds a code g
|
|
|
9
9
|
|
|
10
10
|
**Zero config. No Docker. No Postgres. No Redis. Just install and go.**
|
|
11
11
|
|
|
12
|
-
> **v1.0: Bun-powered, Rust-native parsing.** The CLI now runs exclusively on [Bun](https://bun.sh) 1.1+ with native `bun:sqlite` (no `better-sqlite3` compilation step). TS/JS parsing uses [oxc-parser](https://oxc.rs) (Rust-based, ~13x faster than Babel) instead of `ts-morph`. Python/Go/Rust use tree-sitter
|
|
12
|
+
> **v1.0: Bun-powered, Rust-native parsing.** The CLI now runs exclusively on [Bun](https://bun.sh) 1.1+ with native `bun:sqlite` (no `better-sqlite3` compilation step). TS/JS parsing uses [oxc-parser](https://oxc.rs) (Rust-based, ~13x faster than Babel) instead of `ts-morph`. Python/Go/Rust/Ruby use tree-sitter (WASM) with regex fallback. Requires Bun 1.1+.
|
|
13
13
|
|
|
14
14
|
## Features
|
|
15
15
|
|
|
16
16
|
- **Bun-powered** — native `bun:sqlite` driver, no native compilation, near-instant startup
|
|
17
|
-
- **Rust-native parsing** — [oxc-parser](https://oxc.rs) for TS/JS (~13x faster than Babel), tree-sitter for Python/Go/Rust
|
|
17
|
+
- **Rust-native parsing** — [oxc-parser](https://oxc.rs) for TS/JS (~13x faster than Babel), tree-sitter (WASM) for Python/Go/Rust/Ruby with regex fallback
|
|
18
18
|
- **Zero-config** — auto-registers workspace, auto-indexes; opt-in auto-sync via `OPENEZ_MCP_WATCH=1`
|
|
19
19
|
- **SQLite-first** — all data stored locally in `.openez/` per workspace, no Postgres/Redis
|
|
20
20
|
- **FTS5 full-text search** — SQLite FTS5 with BM25 ranking and porter tokenizer
|
|
@@ -106,14 +106,16 @@ Valid config keys: `embedding.provider`, `embedding.openai_api_key`, `embedding.
|
|
|
106
106
|
|
|
107
107
|
## Supported languages
|
|
108
108
|
|
|
109
|
-
| Language
|
|
110
|
-
|
|
|
111
|
-
| TypeScript / JavaScript
|
|
112
|
-
| Python
|
|
113
|
-
| Go
|
|
114
|
-
| Rust
|
|
115
|
-
|
|
|
116
|
-
|
|
|
109
|
+
| Language | Parser | Indexing depth |
|
|
110
|
+
| ----------------------------------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------- |
|
|
111
|
+
| TypeScript / JavaScript | [oxc-parser](https://oxc.rs) | Richest — symbol extraction, imports, calls (~13x faster than Babel) |
|
|
112
|
+
| Python | tree-sitter (WASM) | Symbol extraction, decorators, imports, calls |
|
|
113
|
+
| Go | tree-sitter (WASM) | Symbol extraction, receiver-qualified methods, calls |
|
|
114
|
+
| Rust | tree-sitter (WASM) | Symbol extraction, impl blocks, calls |
|
|
115
|
+
| Ruby | tree-sitter (WASM) | Symbol extraction, `class << self` context, `self.foo` calls, `require_relative` imports |
|
|
116
|
+
| CoffeeScript / Slim / CSS / SCSS / SASS / LESS / Haml | Fallback parser | Scanned and chunked (no symbol extraction) |
|
|
117
|
+
| YAML / JSON / TOML | Structure-aware | Structure-aware chunking |
|
|
118
|
+
| Markdown | Section-oriented | Section-oriented chunking |
|
|
117
119
|
|
|
118
120
|
## Retrieval quality
|
|
119
121
|
|
package/dist/CHANGELOG.md
CHANGED
|
@@ -5,12 +5,26 @@ All notable changes to OpenEZ Graph are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.3.0] - 2026-08-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Ruby indexing** — tree-sitter WASM symbol extraction for classes, modules, methods, singleton methods, named lambdas, `class << self` nesting, and `require_relative` import edges.
|
|
13
|
+
- **Ruby and frontend language scanning** — `.rb`, `.rake`, `.gemspec`, CoffeeScript, Slim, CSS, SCSS, Sass, Less, and Haml files are now indexed; unsupported languages use fallback chunking.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **Ruby graph extraction** — preserves calls inside ordinary assignments, qualifies `self` calls in class bodies, keeps regex fallback nesting correct, and aligns method export metadata with the AST parser.
|
|
18
|
+
|
|
8
19
|
## [1.2.0] - 2026-08-11
|
|
9
20
|
|
|
10
21
|
### Added
|
|
11
22
|
|
|
12
23
|
- **Lease-based indexing ownership** — indexing claims a 60-second lease with 15-second heartbeat. If the lease expires (e.g. process crash), another process can take over. Completion and failure writes are fenced by owner token — a stale owner cannot overwrite the status set by a newer owner.
|
|
13
24
|
- **Graph build warnings in MCP** — `code_query` response includes a `warnings` array when graph expansion fails, so callers see FTS-only results instead of silently missing graph expansion.
|
|
25
|
+
- **Web UI: Reindex button** — workspace detail page now has a "Reindex workspace" button with loading state and error display, triggering a full rebuild via `startIndexRun(workspaceId, "full")`.
|
|
26
|
+
- **Web UI: Build & Open Graph** — workspace detail page shows "Build & Open Graph" button when graph data is empty, or "Open Graph" when graph exists. Graph is built lazily on first access via `ensureGraphReady`.
|
|
27
|
+
- **Web UI: Local embedding model picker** — settings page now supports selecting local embedding model preset (`jina-code-static-256`) with server-side validation against `LOCAL_EMBEDDING_MODELS` catalog. Provider dropdown includes "OpenEZ local" option alongside OpenAI and Ollama.
|
|
14
28
|
|
|
15
29
|
### Fixed
|
|
16
30
|
|
|
@@ -286,6 +300,7 @@ Remediation release — index/graph correctness, data protection, and web flow f
|
|
|
286
300
|
- CLI npm packaging
|
|
287
301
|
|
|
288
302
|
[1.2.0]: https://github.com/asta-nguyen/openez-graph/compare/v1.1.0...v1.2.0
|
|
303
|
+
[1.3.0]: https://github.com/asta-nguyen/openez-graph/compare/v1.2.0...v1.3.0
|
|
289
304
|
[1.1.0]: https://github.com/asta-nguyen/openez-graph/compare/v1.0.3...v1.1.0
|
|
290
305
|
[1.0.3]: https://github.com/asta-nguyen/openez-graph/compare/v1.0.2...v1.0.3
|
|
291
306
|
[1.0.2]: https://github.com/asta-nguyen/openez-graph/compare/v1.0.1...v1.0.2
|
package/dist/cli.cjs
CHANGED
|
@@ -33034,6 +33034,15 @@ function inferDocumentKind(filePath) {
|
|
|
33034
33034
|
if (codeExtensions.has(extension)) {
|
|
33035
33035
|
return { kind: "code", language: codeExtensions.get(extension) ?? null, extension };
|
|
33036
33036
|
}
|
|
33037
|
+
if (styleExtensions.has(extension)) {
|
|
33038
|
+
return { kind: "code", language: styleExtensions.get(extension) ?? null, extension };
|
|
33039
|
+
}
|
|
33040
|
+
if (templateExtensions.has(extension)) {
|
|
33041
|
+
return { kind: "code", language: templateExtensions.get(extension) ?? null, extension };
|
|
33042
|
+
}
|
|
33043
|
+
if (scriptExtensions.has(extension)) {
|
|
33044
|
+
return { kind: "code", language: scriptExtensions.get(extension) ?? null, extension };
|
|
33045
|
+
}
|
|
33037
33046
|
if (configExtensions.has(extension)) {
|
|
33038
33047
|
return { kind: "config", language: configExtensions.get(extension) ?? null, extension };
|
|
33039
33048
|
}
|
|
@@ -33537,6 +33546,83 @@ function parseRust(content, counter = exactTokenCounter) {
|
|
|
33537
33546
|
callExpressions
|
|
33538
33547
|
};
|
|
33539
33548
|
}
|
|
33549
|
+
function findRubyBlockEnd(codeLines, startIndex) {
|
|
33550
|
+
const openingLine = codeLines[startIndex] ?? "";
|
|
33551
|
+
if (openingLine.includes(";") && /\bend\s*(?:#.*)?$/.test(openingLine)) {
|
|
33552
|
+
return startIndex + 1;
|
|
33553
|
+
}
|
|
33554
|
+
const baseIndent = codeLines[startIndex]?.search(/\S/) ?? 0;
|
|
33555
|
+
for (let i = startIndex + 1; i < codeLines.length; i++) {
|
|
33556
|
+
const trimmed = codeLines[i].trim();
|
|
33557
|
+
if (trimmed === "") continue;
|
|
33558
|
+
const indent = codeLines[i].search(/\S/);
|
|
33559
|
+
if (indent < 0) continue;
|
|
33560
|
+
if (indent < baseIndent) {
|
|
33561
|
+
return i;
|
|
33562
|
+
}
|
|
33563
|
+
if (indent === baseIndent && (trimmed === "end" || trimmed.startsWith("end ") || trimmed.startsWith("end "))) {
|
|
33564
|
+
return i + 1;
|
|
33565
|
+
}
|
|
33566
|
+
}
|
|
33567
|
+
return codeLines.length;
|
|
33568
|
+
}
|
|
33569
|
+
function parseRuby(content, counter = exactTokenCounter) {
|
|
33570
|
+
const lines = content.split("\n");
|
|
33571
|
+
const codeLines = stripNonCode(content, { hashComments: true }).split("\n");
|
|
33572
|
+
const definedSymbols = [];
|
|
33573
|
+
const importPaths = [];
|
|
33574
|
+
const calledIdentifiers = /* @__PURE__ */ new Set();
|
|
33575
|
+
const callExpressions = [];
|
|
33576
|
+
const symbolRegex = /^\s*(?:def\s+(?:self\.)?(\w+[?!]?)|class\s+(\w+(?:::\w+)*)|module\s+(\w+(?:::\w+)*))/;
|
|
33577
|
+
const requireRelativeRegex = /\brequire_relative\s+['"]([^'"]+)['"]/g;
|
|
33578
|
+
const contextStack = [];
|
|
33579
|
+
for (let i = 0; i < codeLines.length; i++) {
|
|
33580
|
+
const line = codeLines[i];
|
|
33581
|
+
const trimmed = line.trim();
|
|
33582
|
+
while (contextStack.length > 0 && i >= contextStack[contextStack.length - 1].endLine) {
|
|
33583
|
+
contextStack.pop();
|
|
33584
|
+
}
|
|
33585
|
+
const match2 = symbolRegex.exec(trimmed);
|
|
33586
|
+
if (match2) {
|
|
33587
|
+
const rawName = match2[1] ?? match2[2] ?? match2[3];
|
|
33588
|
+
if (!rawName) continue;
|
|
33589
|
+
const isMethod = Boolean(match2[1]);
|
|
33590
|
+
const symbolType2 = isMethod ? "function" : match2[2] ? "class" : "module";
|
|
33591
|
+
const parentName = contextStack.length > 0 ? contextStack[contextStack.length - 1].name : null;
|
|
33592
|
+
const name = parentName ? `${parentName}::${rawName}` : rawName;
|
|
33593
|
+
const endLine = findRubyBlockEnd(codeLines, i);
|
|
33594
|
+
definedSymbols.push({
|
|
33595
|
+
name,
|
|
33596
|
+
symbolType: symbolType2,
|
|
33597
|
+
type: symbolType2,
|
|
33598
|
+
exported: isMethod ? false : !rawName.startsWith("_"),
|
|
33599
|
+
startLine: i + 1,
|
|
33600
|
+
endLine
|
|
33601
|
+
});
|
|
33602
|
+
contextStack.push({ name, endLine });
|
|
33603
|
+
continue;
|
|
33604
|
+
}
|
|
33605
|
+
requireRelativeRegex.lastIndex = 0;
|
|
33606
|
+
let reqMatch;
|
|
33607
|
+
while ((reqMatch = requireRelativeRegex.exec(lines[i] ?? "")) !== null) {
|
|
33608
|
+
importPaths.push(reqMatch[1]);
|
|
33609
|
+
}
|
|
33610
|
+
}
|
|
33611
|
+
const chunks2 = createSymbolChunks(definedSymbols, lines, "ruby", counter);
|
|
33612
|
+
if (chunks2.length === 0) {
|
|
33613
|
+
return {
|
|
33614
|
+
...makeFallbackChunks(content, lines, counter),
|
|
33615
|
+
importPaths: [...new Set(importPaths)]
|
|
33616
|
+
};
|
|
33617
|
+
}
|
|
33618
|
+
return {
|
|
33619
|
+
chunks: chunks2,
|
|
33620
|
+
importPaths: [...new Set(importPaths)],
|
|
33621
|
+
definedSymbols,
|
|
33622
|
+
calledIdentifiers: [...calledIdentifiers],
|
|
33623
|
+
callExpressions
|
|
33624
|
+
};
|
|
33625
|
+
}
|
|
33540
33626
|
function indexConfig(content, language, counter = exactTokenCounter) {
|
|
33541
33627
|
switch (language) {
|
|
33542
33628
|
case "yaml":
|
|
@@ -33808,7 +33894,7 @@ function makeFallbackChunks(content, lines, counter = exactTokenCounter) {
|
|
|
33808
33894
|
callExpressions: []
|
|
33809
33895
|
};
|
|
33810
33896
|
}
|
|
33811
|
-
var import_node_path11, codeExtensions, configExtensions, markdownExtensions, PYTHON_CALL_IGNORES, GO_CALL_IGNORES, RUST_CALL_IGNORES;
|
|
33897
|
+
var import_node_path11, codeExtensions, configExtensions, styleExtensions, templateExtensions, scriptExtensions, markdownExtensions, PYTHON_CALL_IGNORES, GO_CALL_IGNORES, RUST_CALL_IGNORES;
|
|
33812
33898
|
var init_languages = __esm({
|
|
33813
33899
|
"../../packages/indexer/src/languages.ts"() {
|
|
33814
33900
|
"use strict";
|
|
@@ -33826,7 +33912,10 @@ var init_languages = __esm({
|
|
|
33826
33912
|
[".cts", "typescript"],
|
|
33827
33913
|
[".py", "python"],
|
|
33828
33914
|
[".go", "go"],
|
|
33829
|
-
[".rs", "rust"]
|
|
33915
|
+
[".rs", "rust"],
|
|
33916
|
+
[".rb", "ruby"],
|
|
33917
|
+
[".rake", "ruby"],
|
|
33918
|
+
[".gemspec", "ruby"]
|
|
33830
33919
|
]);
|
|
33831
33920
|
configExtensions = /* @__PURE__ */ new Map([
|
|
33832
33921
|
[".yaml", "yaml"],
|
|
@@ -33834,6 +33923,20 @@ var init_languages = __esm({
|
|
|
33834
33923
|
[".json", "json"],
|
|
33835
33924
|
[".toml", "toml"]
|
|
33836
33925
|
]);
|
|
33926
|
+
styleExtensions = /* @__PURE__ */ new Map([
|
|
33927
|
+
[".css", "css"],
|
|
33928
|
+
[".scss", "scss"],
|
|
33929
|
+
[".sass", "scss"],
|
|
33930
|
+
[".less", "less"]
|
|
33931
|
+
]);
|
|
33932
|
+
templateExtensions = /* @__PURE__ */ new Map([
|
|
33933
|
+
[".slim", "slim"],
|
|
33934
|
+
[".haml", "haml"]
|
|
33935
|
+
]);
|
|
33936
|
+
scriptExtensions = /* @__PURE__ */ new Map([
|
|
33937
|
+
[".coffee", "coffeescript"],
|
|
33938
|
+
[".litcoffee", "coffeescript"]
|
|
33939
|
+
]);
|
|
33837
33940
|
markdownExtensions = /* @__PURE__ */ new Set([".md", ".mdx"]);
|
|
33838
33941
|
PYTHON_CALL_IGNORES = /* @__PURE__ */ new Set([
|
|
33839
33942
|
"if",
|
|
@@ -34863,44 +34966,62 @@ function walkTree(root, config2, lines) {
|
|
|
34863
34966
|
}
|
|
34864
34967
|
const symbolRule = symbolRuleMap.get(node.type);
|
|
34865
34968
|
if (symbolRule) {
|
|
34866
|
-
|
|
34867
|
-
|
|
34868
|
-
|
|
34869
|
-
|
|
34870
|
-
|
|
34871
|
-
|
|
34872
|
-
|
|
34873
|
-
|
|
34874
|
-
|
|
34875
|
-
|
|
34876
|
-
|
|
34877
|
-
|
|
34878
|
-
|
|
34879
|
-
|
|
34880
|
-
|
|
34881
|
-
|
|
34882
|
-
|
|
34883
|
-
|
|
34884
|
-
|
|
34885
|
-
|
|
34886
|
-
|
|
34887
|
-
|
|
34888
|
-
|
|
34889
|
-
|
|
34890
|
-
|
|
34891
|
-
|
|
34892
|
-
|
|
34893
|
-
|
|
34894
|
-
|
|
34895
|
-
|
|
34896
|
-
|
|
34897
|
-
|
|
34898
|
-
|
|
34899
|
-
|
|
34900
|
-
|
|
34901
|
-
|
|
34902
|
-
const
|
|
34903
|
-
|
|
34969
|
+
if (symbolRule.contextOnly) {
|
|
34970
|
+
const contextName = symbolRule.extractContextName ? symbolRule.extractContextName(node, contextStack) ?? null : null;
|
|
34971
|
+
if (contextName) {
|
|
34972
|
+
contextStack.push({
|
|
34973
|
+
name: contextName,
|
|
34974
|
+
endRow,
|
|
34975
|
+
kind: symbolRule.contextKind
|
|
34976
|
+
});
|
|
34977
|
+
}
|
|
34978
|
+
} else {
|
|
34979
|
+
const name = symbolRule.extractName ? symbolRule.extractName(node) : getNodeName(node, symbolRule.nameField ?? "name");
|
|
34980
|
+
if (name) {
|
|
34981
|
+
const parentName = contextStack.length > 0 ? contextStack[contextStack.length - 1].name : null;
|
|
34982
|
+
const exported = symbolRule.isExported ? symbolRule.isExported(name, node) : false;
|
|
34983
|
+
let receiver;
|
|
34984
|
+
let receiverVar = null;
|
|
34985
|
+
let receiverType = null;
|
|
34986
|
+
if (symbolRule.receiverField) {
|
|
34987
|
+
const rawReceiver = node.childForFieldName(symbolRule.receiverField)?.text;
|
|
34988
|
+
if (rawReceiver) {
|
|
34989
|
+
receiver = symbolRule.normalizeReceiver ? symbolRule.normalizeReceiver(rawReceiver) : rawReceiver;
|
|
34990
|
+
receiverVar = goReceiverName(rawReceiver);
|
|
34991
|
+
receiverType = goReceiverType(rawReceiver);
|
|
34992
|
+
}
|
|
34993
|
+
}
|
|
34994
|
+
const fullName = receiverType ? `${receiverType}::${name}` : parentName ? `${parentName}::${name}` : name;
|
|
34995
|
+
symbols.push({
|
|
34996
|
+
name: fullName,
|
|
34997
|
+
symbolType: symbolRule.symbolType,
|
|
34998
|
+
type: symbolRule.symbolType,
|
|
34999
|
+
exported,
|
|
35000
|
+
startLine: startRow,
|
|
35001
|
+
endLine: endRow,
|
|
35002
|
+
...receiver ? { receiver } : {}
|
|
35003
|
+
});
|
|
35004
|
+
const receiverInfo = receiverVar && receiverType ? { varName: receiverVar, typeName: receiverType } : void 0;
|
|
35005
|
+
const isContextNode = symbolRule.establishesContext || config2.contextNodeTypes.has(node.type);
|
|
35006
|
+
const contextName = isContextNode ? symbolRule.extractContextName?.(node, contextStack) ?? fullName : null;
|
|
35007
|
+
const callContext = contextName && symbolRule.contextKind ? [...contextStack, { name: contextName, endRow, kind: symbolRule.contextKind }] : contextStack;
|
|
35008
|
+
extractCallsInNode(
|
|
35009
|
+
node,
|
|
35010
|
+
config2,
|
|
35011
|
+
fullName,
|
|
35012
|
+
receiverInfo,
|
|
35013
|
+
calledIdentifiers,
|
|
35014
|
+
callExpressions,
|
|
35015
|
+
callContext
|
|
35016
|
+
);
|
|
35017
|
+
if (isContextNode) {
|
|
35018
|
+
contextStack.push({
|
|
35019
|
+
name: contextName ?? fullName,
|
|
35020
|
+
endRow,
|
|
35021
|
+
kind: symbolRule.contextKind,
|
|
35022
|
+
receiver: receiverInfo
|
|
35023
|
+
});
|
|
35024
|
+
}
|
|
34904
35025
|
}
|
|
34905
35026
|
}
|
|
34906
35027
|
} else if (isCallNode(node, config2.callRule)) {
|
|
@@ -34919,9 +35040,19 @@ function walkTree(root, config2, lines) {
|
|
|
34919
35040
|
}
|
|
34920
35041
|
return { symbols, importPaths, calledIdentifiers, callExpressions };
|
|
34921
35042
|
}
|
|
34922
|
-
function extractCallsInNode(symbolNode, config2, callerName, receiverInfo, calledIdentifiers, callExpressions) {
|
|
34923
|
-
const
|
|
34924
|
-
const nestedSymbols = symbolNode.descendantsOfType(
|
|
35043
|
+
function extractCallsInNode(symbolNode, config2, callerName, receiverInfo, calledIdentifiers, callExpressions, contextStack) {
|
|
35044
|
+
const symbolRuleMap = new Map(config2.symbolRules.map((rule) => [rule.nodeType, rule]));
|
|
35045
|
+
const nestedSymbols = symbolNode.descendantsOfType(config2.symbolRules.map((rule) => rule.nodeType)).filter((node) => {
|
|
35046
|
+
if (node.startIndex === symbolNode.startIndex && node.endIndex === symbolNode.endIndex) {
|
|
35047
|
+
return false;
|
|
35048
|
+
}
|
|
35049
|
+
const rule = symbolRuleMap.get(node.type);
|
|
35050
|
+
if (!rule) return false;
|
|
35051
|
+
if (rule.contextOnly) return true;
|
|
35052
|
+
return Boolean(
|
|
35053
|
+
rule.extractName ? rule.extractName(node) : getNodeName(node, rule.nameField ?? "name")
|
|
35054
|
+
);
|
|
35055
|
+
});
|
|
34925
35056
|
const callNodes = symbolNode.descendantsOfType(config2.callRule.nodeType);
|
|
34926
35057
|
for (const callNode of callNodes) {
|
|
34927
35058
|
const insideNested = nestedSymbols.some(
|
|
@@ -34932,11 +35063,13 @@ function extractCallsInNode(symbolNode, config2, callerName, receiverInfo, calle
|
|
|
34932
35063
|
if (!calleeName2) continue;
|
|
34933
35064
|
const normalized = config2.normalizeCallName(calleeName2);
|
|
34934
35065
|
const qualifiedCallee = receiverInfo && calleeName2.startsWith(`${receiverInfo.varName}.`) ? `${receiverInfo.typeName}::${normalized}` : normalized;
|
|
34935
|
-
|
|
35066
|
+
const hookQualified = config2.qualifyCall?.(callNode, calleeName2, normalized, contextStack);
|
|
35067
|
+
const finalCallee = hookQualified ?? qualifiedCallee;
|
|
35068
|
+
if (!normalized || config2.callIgnores.has(calleeName2) || config2.callIgnores.has(normalized) || finalCallee === callerName) {
|
|
34936
35069
|
continue;
|
|
34937
35070
|
}
|
|
34938
|
-
calledIdentifiers.add(
|
|
34939
|
-
callExpressions.push({ callerName, calleeName:
|
|
35071
|
+
calledIdentifiers.add(finalCallee);
|
|
35072
|
+
callExpressions.push({ callerName, calleeName: finalCallee });
|
|
34940
35073
|
}
|
|
34941
35074
|
}
|
|
34942
35075
|
async function parseWithTreeSitter(config2, content, counter) {
|
|
@@ -35053,7 +35186,42 @@ function extractRustImports(node) {
|
|
|
35053
35186
|
}
|
|
35054
35187
|
return paths;
|
|
35055
35188
|
}
|
|
35056
|
-
|
|
35189
|
+
function normalizeRubyCallName(value) {
|
|
35190
|
+
const parts = value.split(/[.::]/).filter(Boolean);
|
|
35191
|
+
return parts[parts.length - 1] ?? value;
|
|
35192
|
+
}
|
|
35193
|
+
function extractRubyImports(node) {
|
|
35194
|
+
const paths = [];
|
|
35195
|
+
if (node.type === "call") {
|
|
35196
|
+
const methodNode = node.childForFieldName("method");
|
|
35197
|
+
if (methodNode && methodNode.text === "require_relative") {
|
|
35198
|
+
const argsNode = node.childForFieldName("arguments");
|
|
35199
|
+
if (argsNode) {
|
|
35200
|
+
const stringNode = argsNode.namedChildren.find(
|
|
35201
|
+
(c) => c.type === "string" || c.type === "argument_list"
|
|
35202
|
+
);
|
|
35203
|
+
const target = stringNode?.type === "string" ? stringNode : stringNode?.namedChildren.find((c) => c.type === "string");
|
|
35204
|
+
if (target) {
|
|
35205
|
+
const text2 = target.text.replace(/^['"]|['"]$/g, "");
|
|
35206
|
+
if (text2) paths.push(text2);
|
|
35207
|
+
}
|
|
35208
|
+
}
|
|
35209
|
+
}
|
|
35210
|
+
}
|
|
35211
|
+
return paths;
|
|
35212
|
+
}
|
|
35213
|
+
function rubyQualifyCall(callNode, _calleeName, normalized, contextStack) {
|
|
35214
|
+
const receiverNode = callNode.childForFieldName("receiver");
|
|
35215
|
+
if (!receiverNode || receiverNode.text !== "self") return null;
|
|
35216
|
+
for (let i = contextStack.length - 1; i >= 0; i--) {
|
|
35217
|
+
const frame = contextStack[i];
|
|
35218
|
+
if (frame.kind === "class" || frame.kind === "module") {
|
|
35219
|
+
return `${frame.name}::${normalized}`;
|
|
35220
|
+
}
|
|
35221
|
+
}
|
|
35222
|
+
return null;
|
|
35223
|
+
}
|
|
35224
|
+
var PYTHON_CALL_IGNORES2, pythonConfig, GO_CALL_IGNORES2, goConfig, RUST_CALL_IGNORES2, rustConfig, RUBY_CALL_IGNORES, rubyConfig;
|
|
35057
35225
|
var init_configs = __esm({
|
|
35058
35226
|
"../../packages/indexer/src/tree-sitter/configs.ts"() {
|
|
35059
35227
|
"use strict";
|
|
@@ -35280,7 +35448,7 @@ var init_configs = __esm({
|
|
|
35280
35448
|
}
|
|
35281
35449
|
return typeName ? `impl ${typeName}` : null;
|
|
35282
35450
|
},
|
|
35283
|
-
extractContextName: (node) => {
|
|
35451
|
+
extractContextName: (node, _contextStack) => {
|
|
35284
35452
|
return node.childForFieldName("type")?.text ?? null;
|
|
35285
35453
|
},
|
|
35286
35454
|
establishesContext: true,
|
|
@@ -35318,6 +35486,132 @@ var init_configs = __esm({
|
|
|
35318
35486
|
contextNodeTypes: /* @__PURE__ */ new Set(["function_item", "trait_item", "impl_item"]),
|
|
35319
35487
|
contextNameField: "name"
|
|
35320
35488
|
};
|
|
35489
|
+
RUBY_CALL_IGNORES = /* @__PURE__ */ new Set([
|
|
35490
|
+
"if",
|
|
35491
|
+
"unless",
|
|
35492
|
+
"while",
|
|
35493
|
+
"until",
|
|
35494
|
+
"for",
|
|
35495
|
+
"return",
|
|
35496
|
+
"yield",
|
|
35497
|
+
"break",
|
|
35498
|
+
"next",
|
|
35499
|
+
"redo",
|
|
35500
|
+
"retry",
|
|
35501
|
+
"puts",
|
|
35502
|
+
"pp",
|
|
35503
|
+
"p",
|
|
35504
|
+
"print",
|
|
35505
|
+
"raise",
|
|
35506
|
+
"require",
|
|
35507
|
+
"require_relative",
|
|
35508
|
+
"load",
|
|
35509
|
+
"autoload",
|
|
35510
|
+
"attr_accessor",
|
|
35511
|
+
"attr_reader",
|
|
35512
|
+
"attr_writer",
|
|
35513
|
+
"include",
|
|
35514
|
+
"extend",
|
|
35515
|
+
"define_method",
|
|
35516
|
+
"lambda",
|
|
35517
|
+
"proc",
|
|
35518
|
+
"super",
|
|
35519
|
+
"self",
|
|
35520
|
+
"nil",
|
|
35521
|
+
"true",
|
|
35522
|
+
"false",
|
|
35523
|
+
"new",
|
|
35524
|
+
"Array",
|
|
35525
|
+
"Hash",
|
|
35526
|
+
"String",
|
|
35527
|
+
"Integer",
|
|
35528
|
+
"Float",
|
|
35529
|
+
"Symbol"
|
|
35530
|
+
]);
|
|
35531
|
+
rubyConfig = {
|
|
35532
|
+
language: "ruby",
|
|
35533
|
+
symbolRules: [
|
|
35534
|
+
{
|
|
35535
|
+
nodeType: "class",
|
|
35536
|
+
symbolType: "class",
|
|
35537
|
+
nameField: "name",
|
|
35538
|
+
establishesContext: true,
|
|
35539
|
+
contextKind: "class",
|
|
35540
|
+
isExported: (name) => name[0] >= "A" && name[0] <= "Z"
|
|
35541
|
+
},
|
|
35542
|
+
{
|
|
35543
|
+
nodeType: "module",
|
|
35544
|
+
symbolType: "module",
|
|
35545
|
+
nameField: "name",
|
|
35546
|
+
establishesContext: true,
|
|
35547
|
+
contextKind: "module",
|
|
35548
|
+
isExported: (name) => name[0] >= "A" && name[0] <= "Z"
|
|
35549
|
+
},
|
|
35550
|
+
{
|
|
35551
|
+
nodeType: "method",
|
|
35552
|
+
symbolType: "function",
|
|
35553
|
+
nameField: "name",
|
|
35554
|
+
establishesContext: true,
|
|
35555
|
+
isExported: () => false
|
|
35556
|
+
},
|
|
35557
|
+
{
|
|
35558
|
+
nodeType: "singleton_method",
|
|
35559
|
+
symbolType: "function",
|
|
35560
|
+
nameField: "name",
|
|
35561
|
+
establishesContext: true,
|
|
35562
|
+
isExported: () => false
|
|
35563
|
+
},
|
|
35564
|
+
{
|
|
35565
|
+
nodeType: "singleton_class",
|
|
35566
|
+
symbolType: "class",
|
|
35567
|
+
contextOnly: true,
|
|
35568
|
+
extractContextName: (_node, contextStack) => {
|
|
35569
|
+
for (let i = contextStack.length - 1; i >= 0; i--) {
|
|
35570
|
+
const frame = contextStack[i];
|
|
35571
|
+
if (frame.kind === "class" || frame.kind === "module") {
|
|
35572
|
+
return frame.name;
|
|
35573
|
+
}
|
|
35574
|
+
}
|
|
35575
|
+
return null;
|
|
35576
|
+
},
|
|
35577
|
+
isExported: () => false
|
|
35578
|
+
},
|
|
35579
|
+
{
|
|
35580
|
+
nodeType: "assignment",
|
|
35581
|
+
symbolType: "lambda",
|
|
35582
|
+
extractName: (node) => {
|
|
35583
|
+
const rightNode = node.childForFieldName("right");
|
|
35584
|
+
if (!rightNode) return null;
|
|
35585
|
+
if (rightNode.type === "lambda") {
|
|
35586
|
+
const leftNode = node.childForFieldName("left");
|
|
35587
|
+
return leftNode?.text ?? null;
|
|
35588
|
+
}
|
|
35589
|
+
if (rightNode.type === "call") {
|
|
35590
|
+
const methodNode = rightNode.childForFieldName("method");
|
|
35591
|
+
const methodName = methodNode?.text;
|
|
35592
|
+
if (methodName === "proc" || methodName === "lambda") {
|
|
35593
|
+
const leftNode = node.childForFieldName("left");
|
|
35594
|
+
return leftNode?.text ?? null;
|
|
35595
|
+
}
|
|
35596
|
+
const receiverNode = rightNode.childForFieldName("receiver");
|
|
35597
|
+
if (receiverNode?.text === "Proc" && methodName === "new") {
|
|
35598
|
+
const leftNode = node.childForFieldName("left");
|
|
35599
|
+
return leftNode?.text ?? null;
|
|
35600
|
+
}
|
|
35601
|
+
}
|
|
35602
|
+
return null;
|
|
35603
|
+
},
|
|
35604
|
+
isExported: () => false
|
|
35605
|
+
}
|
|
35606
|
+
],
|
|
35607
|
+
importRules: [{ nodeType: "call", extract: extractRubyImports }],
|
|
35608
|
+
callRule: { nodeType: "call", functionField: "method" },
|
|
35609
|
+
callIgnores: RUBY_CALL_IGNORES,
|
|
35610
|
+
normalizeCallName: normalizeRubyCallName,
|
|
35611
|
+
contextNodeTypes: /* @__PURE__ */ new Set(["class", "module", "method", "singleton_method"]),
|
|
35612
|
+
contextNameField: "name",
|
|
35613
|
+
qualifyCall: rubyQualifyCall
|
|
35614
|
+
};
|
|
35321
35615
|
}
|
|
35322
35616
|
});
|
|
35323
35617
|
|
|
@@ -35345,12 +35639,14 @@ var init_tree_sitter_parser = __esm({
|
|
|
35345
35639
|
LANGUAGE_CONFIGS = {
|
|
35346
35640
|
python: pythonConfig,
|
|
35347
35641
|
go: goConfig,
|
|
35348
|
-
rust: rustConfig
|
|
35642
|
+
rust: rustConfig,
|
|
35643
|
+
ruby: rubyConfig
|
|
35349
35644
|
};
|
|
35350
35645
|
REGEX_FALLBACKS = {
|
|
35351
35646
|
python: parsePython,
|
|
35352
35647
|
go: parseGo,
|
|
35353
|
-
rust: parseRust
|
|
35648
|
+
rust: parseRust,
|
|
35649
|
+
ruby: parseRuby
|
|
35354
35650
|
};
|
|
35355
35651
|
TreeSitterParser = class {
|
|
35356
35652
|
name = "tree-sitter";
|
|
@@ -43052,12 +43348,18 @@ var init_scanner = __esm({
|
|
|
43052
43348
|
DEFAULT_INCLUDE_PATTERNS = [
|
|
43053
43349
|
...Array.from(codeExtensions.keys()).map((ext) => `**/*${ext}`),
|
|
43054
43350
|
...Array.from(configExtensions.keys()).map((ext) => `**/*${ext}`),
|
|
43055
|
-
...Array.from(markdownExtensions).map((ext) => `**/*${ext}`)
|
|
43351
|
+
...Array.from(markdownExtensions).map((ext) => `**/*${ext}`),
|
|
43352
|
+
...Array.from(styleExtensions.keys()).map((ext) => `**/*${ext}`),
|
|
43353
|
+
...Array.from(templateExtensions.keys()).map((ext) => `**/*${ext}`),
|
|
43354
|
+
...Array.from(scriptExtensions.keys()).map((ext) => `**/*${ext}`)
|
|
43056
43355
|
];
|
|
43057
43356
|
ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
43058
43357
|
...codeExtensions.keys(),
|
|
43059
43358
|
...configExtensions.keys(),
|
|
43060
|
-
...markdownExtensions
|
|
43359
|
+
...markdownExtensions,
|
|
43360
|
+
...styleExtensions.keys(),
|
|
43361
|
+
...templateExtensions.keys(),
|
|
43362
|
+
...scriptExtensions.keys()
|
|
43061
43363
|
]);
|
|
43062
43364
|
DEFAULT_EXCLUDE_PATTERNS = [
|
|
43063
43365
|
"**/node_modules",
|
|
@@ -43178,6 +43480,9 @@ function createWorkspaceFileResolver(workspaceRoot, files) {
|
|
|
43178
43480
|
const resolved = resolvePythonImport(importerRelativePath, importPath);
|
|
43179
43481
|
if (resolved) return resolved;
|
|
43180
43482
|
}
|
|
43483
|
+
if (language === "ruby") {
|
|
43484
|
+
return resolveRelativeImport(importerRelativePath, importPath);
|
|
43485
|
+
}
|
|
43181
43486
|
if (importPath.startsWith(".")) {
|
|
43182
43487
|
return resolveRelativeImport(importerRelativePath, importPath);
|
|
43183
43488
|
}
|
|
@@ -44116,7 +44421,8 @@ var init_index_workspace = __esm({
|
|
|
44116
44421
|
".cts",
|
|
44117
44422
|
".md",
|
|
44118
44423
|
".mdx",
|
|
44119
|
-
".py"
|
|
44424
|
+
".py",
|
|
44425
|
+
".rb"
|
|
44120
44426
|
];
|
|
44121
44427
|
PARSER_VERSION_OXC = "oxc-v2";
|
|
44122
44428
|
PARSER_VERSION_NATIVE = "native-v1";
|
|
@@ -59961,7 +60267,7 @@ __export(mcp_bridge_exports, {
|
|
|
59961
60267
|
startMcpServer: () => startMcpServer
|
|
59962
60268
|
});
|
|
59963
60269
|
async function startMcpServer(defaultPath, version4) {
|
|
59964
|
-
await createAndStartMcpServer({ defaultPath, version: version4, build: "
|
|
60270
|
+
await createAndStartMcpServer({ defaultPath, version: version4, build: "dcb4ee5-dirty" });
|
|
59965
60271
|
}
|
|
59966
60272
|
var init_mcp_bridge = __esm({
|
|
59967
60273
|
"src/mcp-bridge.ts"() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openez-graph/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Local-first code intelligence engine — index, query, and graph your codebase with zero config. SQLite-only, MCP-first.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Asta Nguyen",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"oxc-parser": "^0.143.0",
|
|
52
52
|
"tree-sitter-go": "^0.25.0",
|
|
53
53
|
"tree-sitter-python": "^0.25.0",
|
|
54
|
+
"tree-sitter-ruby": "^0.23.1",
|
|
54
55
|
"tree-sitter-rust": "^0.24.0",
|
|
55
56
|
"web-tree-sitter": "^0.26.11"
|
|
56
57
|
},
|