rho-graph 0.1.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 +277 -0
- package/bin/rho-graph.js +2 -0
- package/dist/cli/commands/index-cmd.d.ts +2 -0
- package/dist/cli/commands/index-cmd.js +45 -0
- package/dist/cli/commands/index-cmd.js.map +1 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.js +55 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/install-hook.d.ts +3 -0
- package/dist/cli/commands/install-hook.js +37 -0
- package/dist/cli/commands/install-hook.js.map +1 -0
- package/dist/cli/commands/install-mcp.d.ts +3 -0
- package/dist/cli/commands/install-mcp.js +32 -0
- package/dist/cli/commands/install-mcp.js.map +1 -0
- package/dist/cli/commands/query.d.ts +2 -0
- package/dist/cli/commands/query.js +92 -0
- package/dist/cli/commands/query.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +2 -0
- package/dist/cli/commands/setup.js +15 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/commands/status.d.ts +2 -0
- package/dist/cli/commands/status.js +40 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/visualize.d.ts +2 -0
- package/dist/cli/commands/visualize.js +45 -0
- package/dist/cli/commands/visualize.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +32 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.js +95 -0
- package/dist/config.js.map +1 -0
- package/dist/db/connection.d.ts +13 -0
- package/dist/db/connection.js +25 -0
- package/dist/db/connection.js.map +1 -0
- package/dist/db/queries.d.ts +106 -0
- package/dist/db/queries.js +247 -0
- package/dist/db/queries.js.map +1 -0
- package/dist/db/schema.d.ts +2 -0
- package/dist/db/schema.js +22 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/docker/neo4j.d.ts +5 -0
- package/dist/docker/neo4j.js +85 -0
- package/dist/docker/neo4j.js.map +1 -0
- package/dist/indexer/batch-writer.d.ts +35 -0
- package/dist/indexer/batch-writer.js +202 -0
- package/dist/indexer/batch-writer.js.map +1 -0
- package/dist/indexer/extractor.d.ts +35 -0
- package/dist/indexer/extractor.js +141 -0
- package/dist/indexer/extractor.js.map +1 -0
- package/dist/indexer/graph-writer.d.ts +12 -0
- package/dist/indexer/graph-writer.js +75 -0
- package/dist/indexer/graph-writer.js.map +1 -0
- package/dist/indexer/import-resolver.d.ts +8 -0
- package/dist/indexer/import-resolver.js +80 -0
- package/dist/indexer/import-resolver.js.map +1 -0
- package/dist/indexer/index.d.ts +21 -0
- package/dist/indexer/index.js +262 -0
- package/dist/indexer/index.js.map +1 -0
- package/dist/indexer/language-map.json +101 -0
- package/dist/indexer/parallel-pipeline.d.ts +41 -0
- package/dist/indexer/parallel-pipeline.js +82 -0
- package/dist/indexer/parallel-pipeline.js.map +1 -0
- package/dist/indexer/parser.d.ts +9 -0
- package/dist/indexer/parser.js +85 -0
- package/dist/indexer/parser.js.map +1 -0
- package/dist/indexer/staleness.d.ts +12 -0
- package/dist/indexer/staleness.js +60 -0
- package/dist/indexer/staleness.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +38 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/staleness-check.d.ts +7 -0
- package/dist/mcp/staleness-check.js +31 -0
- package/dist/mcp/staleness-check.js.map +1 -0
- package/dist/mcp/tools/get-callees.d.ts +3 -0
- package/dist/mcp/tools/get-callees.js +34 -0
- package/dist/mcp/tools/get-callees.js.map +1 -0
- package/dist/mcp/tools/get-callers.d.ts +3 -0
- package/dist/mcp/tools/get-callers.js +34 -0
- package/dist/mcp/tools/get-callers.js.map +1 -0
- package/dist/mcp/tools/get-class.d.ts +3 -0
- package/dist/mcp/tools/get-class.js +42 -0
- package/dist/mcp/tools/get-class.js.map +1 -0
- package/dist/mcp/tools/get-dependencies.d.ts +3 -0
- package/dist/mcp/tools/get-dependencies.js +26 -0
- package/dist/mcp/tools/get-dependencies.js.map +1 -0
- package/dist/mcp/tools/get-dependents.d.ts +3 -0
- package/dist/mcp/tools/get-dependents.js +26 -0
- package/dist/mcp/tools/get-dependents.js.map +1 -0
- package/dist/mcp/tools/get-file-structure.d.ts +3 -0
- package/dist/mcp/tools/get-file-structure.js +33 -0
- package/dist/mcp/tools/get-file-structure.js.map +1 -0
- package/dist/mcp/tools/get-function.d.ts +3 -0
- package/dist/mcp/tools/get-function.js +34 -0
- package/dist/mcp/tools/get-function.js.map +1 -0
- package/dist/mcp/tools/get-repo-structure.d.ts +3 -0
- package/dist/mcp/tools/get-repo-structure.js +39 -0
- package/dist/mcp/tools/get-repo-structure.js.map +1 -0
- package/dist/mcp/tools/reindex.d.ts +4 -0
- package/dist/mcp/tools/reindex.js +27 -0
- package/dist/mcp/tools/reindex.js.map +1 -0
- package/dist/mcp/tools/search-code.d.ts +3 -0
- package/dist/mcp/tools/search-code.js +43 -0
- package/dist/mcp/tools/search-code.js.map +1 -0
- package/dist/visualize/public/graph.js +445 -0
- package/dist/visualize/public/index.html +88 -0
- package/dist/visualize/queries.d.ts +14 -0
- package/dist/visualize/queries.js +84 -0
- package/dist/visualize/queries.js.map +1 -0
- package/dist/visualize/server.d.ts +19 -0
- package/dist/visualize/server.js +293 -0
- package/dist/visualize/server.js.map +1 -0
- package/docker-compose.yml +16 -0
- package/package.json +69 -0
- package/src/indexer/language-map.json +128 -0
- package/src/visualize/public/graph.js +445 -0
- package/src/visualize/public/index.html +88 -0
package/README.md
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# rho-graph
|
|
2
|
+
|
|
3
|
+
> Graph-RAG code indexer for AI agents — token-efficient code search via MCP tools backed by Neo4j
|
|
4
|
+
|
|
5
|
+
Parses your codebase into a knowledge graph (functions, classes, imports, call edges) and exposes it as MCP tools that Claude Code, Cursor, and other AI agents can use instead of reading raw files. A graph traversal to find callers of a function costs a handful of tokens; reading every file to find the same thing costs thousands.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## How it works
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Your code
|
|
13
|
+
│
|
|
14
|
+
▼ tree-sitter (WASM, no native deps)
|
|
15
|
+
Parse AST
|
|
16
|
+
│
|
|
17
|
+
▼ extractor
|
|
18
|
+
Functions · Classes · Imports · Call edges
|
|
19
|
+
│
|
|
20
|
+
▼ graph-writer
|
|
21
|
+
Neo4j property graph
|
|
22
|
+
│
|
|
23
|
+
▼ MCP server (stdio)
|
|
24
|
+
Claude Code / Cursor get 10 graph tools
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Files are parsed with [tree-sitter](https://tree-sitter.github.io/) via WASM bindings — no native compilation required. The resulting entities are written to Neo4j with uniqueness constraints and full-text indexes so queries are fast even on large repos.
|
|
28
|
+
|
|
29
|
+
Incremental indexing checks files against stored content hashes and mtimes so only changed files are re-parsed on subsequent runs.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Supported languages
|
|
34
|
+
|
|
35
|
+
TypeScript · TSX · JavaScript · Python · Go · Java · Rust · C · C++ · Ruby · GraphQL · YAML · JSON · Markdown
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Quick start
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Install globally
|
|
43
|
+
npm install -g rho-graph
|
|
44
|
+
|
|
45
|
+
# In your repo: start Neo4j, index, register MCP, install git hook
|
|
46
|
+
rho-graph setup
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
That's it. Claude Code and Cursor will now have access to the graph tools for the current repo.
|
|
50
|
+
|
|
51
|
+
**Prerequisites:** Docker (for the managed Neo4j instance) and Node.js >= 18.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## CLI reference
|
|
56
|
+
|
|
57
|
+
| Command | Description |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `setup` | One-shot: runs `init` + `install-mcp` + `install-hook` |
|
|
60
|
+
| `init` | Start Neo4j (Docker) and index the current repo |
|
|
61
|
+
| `index [path]` | Re-index (full or `--changed` for incremental) |
|
|
62
|
+
| `status` | Show graph stats: repos, files by language, function/class counts |
|
|
63
|
+
| `install-mcp` | Register the MCP server in `~/.claude/settings.json` and `~/.cursor/mcp.json` |
|
|
64
|
+
| `install-hook` | Install `.git/hooks/post-commit` to auto-reindex on commit |
|
|
65
|
+
| `query [cypher]` | Run a Cypher query or open an interactive REPL |
|
|
66
|
+
| `visualize` | Open a browser-based graph visualization on port 3333 |
|
|
67
|
+
| `mcp-serve` | Start the MCP server (called automatically by Claude Code / Cursor) |
|
|
68
|
+
|
|
69
|
+
### `index` options
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
rho-graph index # full reindex
|
|
73
|
+
rho-graph index --changed # only files changed since last index
|
|
74
|
+
rho-graph index src/auth # index a subtree
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### `query` examples
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Shorthand flags
|
|
81
|
+
rho-graph query --callers processPayment
|
|
82
|
+
rho-graph query --dependencies src/auth/middleware.ts
|
|
83
|
+
rho-graph query --structure
|
|
84
|
+
|
|
85
|
+
# Raw Cypher
|
|
86
|
+
rho-graph query "MATCH (f:Function) WHERE f.name STARTS WITH 'get' RETURN f.name, f.filePath LIMIT 20"
|
|
87
|
+
|
|
88
|
+
# Interactive REPL
|
|
89
|
+
rho-graph query
|
|
90
|
+
cypher> MATCH (c:Class)-[:CONTAINS]->(f:Function) RETURN c.name, count(f) ORDER BY count(f) DESC
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### `visualize` options
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
rho-graph visualize # full graph
|
|
97
|
+
rho-graph visualize --repo my-service # filter by repo
|
|
98
|
+
rho-graph visualize --file src/api.ts # focus on a file
|
|
99
|
+
rho-graph visualize --function handleRequest
|
|
100
|
+
rho-graph visualize --port 4000
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## MCP tools
|
|
106
|
+
|
|
107
|
+
Once installed, Claude Code and Cursor have access to these tools. They are designed to be used instead of reading source files.
|
|
108
|
+
|
|
109
|
+
| Tool | What it does |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `search_code` | Full-text search across function/class names, signatures, and code snippets |
|
|
112
|
+
| `get_function` | Retrieve a function's source, signature, file, and line range |
|
|
113
|
+
| `get_class` | Retrieve a class definition and its members |
|
|
114
|
+
| `get_file_structure` | List all functions and classes defined in a file |
|
|
115
|
+
| `get_callers` | Find every function that calls a given function |
|
|
116
|
+
| `get_callees` | Find every function called by a given function |
|
|
117
|
+
| `get_dependencies` | Files imported by a given file |
|
|
118
|
+
| `get_dependents` | Files that import a given file |
|
|
119
|
+
| `get_repo_structure` | High-level view: repos, files per language |
|
|
120
|
+
| `reindex` | Trigger incremental reindex from within a conversation |
|
|
121
|
+
|
|
122
|
+
### Example agent workflow
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
Agent: search_code("authentication middleware")
|
|
126
|
+
→ finds handleAuth in src/auth/middleware.ts
|
|
127
|
+
|
|
128
|
+
Agent: get_callers("handleAuth")
|
|
129
|
+
→ finds 3 routes that depend on it
|
|
130
|
+
|
|
131
|
+
Agent: get_function("handleAuth", "src/auth/middleware.ts")
|
|
132
|
+
→ retrieves the full source + signature
|
|
133
|
+
|
|
134
|
+
Agent: get_dependencies("src/auth/middleware.ts")
|
|
135
|
+
→ finds what it imports, traces the dependency chain
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Graph schema
|
|
141
|
+
|
|
142
|
+
**Nodes**
|
|
143
|
+
|
|
144
|
+
| Label | Key properties |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `Repository` | `path`, `name`, `lastIndexedAt` |
|
|
147
|
+
| `File` | `path`, `relativePath`, `language`, `hash`, `lastModified` |
|
|
148
|
+
| `Function` | `name`, `filePath`, `signature`, `snippet`, `startLine`, `endLine` |
|
|
149
|
+
| `Class` | `name`, `filePath`, `snippet`, `startLine`, `endLine` |
|
|
150
|
+
|
|
151
|
+
**Relationships**
|
|
152
|
+
|
|
153
|
+
| Relationship | Meaning |
|
|
154
|
+
|---|---|
|
|
155
|
+
| `(Repository)-[:CONTAINS_FILE]->(File)` | Repo owns file |
|
|
156
|
+
| `(File)-[:CONTAINS]->(Function\|Class)` | File contains top-level definition |
|
|
157
|
+
| `(Class)-[:HAS_METHOD]->(Function)` | Class method |
|
|
158
|
+
| `(Function)-[:CALLS]->(Function)` | Call edge |
|
|
159
|
+
| `(File)-[:IMPORTS]->(File)` | Import edge |
|
|
160
|
+
| `(File)-[:IMPORTS_SYMBOL]->(Function\|Class)` | Symbol-level import |
|
|
161
|
+
|
|
162
|
+
**Indexes**
|
|
163
|
+
|
|
164
|
+
- Uniqueness constraints on `Repository.path` and `File.path`
|
|
165
|
+
- B-tree indexes on `Function.name`, `Class.name`, `File.language`
|
|
166
|
+
- Full-text index (`code_search`) on `Function.name`, `Function.snippet`, `Function.docstring`, `Class.name`, `Class.snippet`, `Class.docstring`
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Configuration
|
|
171
|
+
|
|
172
|
+
Config is resolved in this order (later values win):
|
|
173
|
+
|
|
174
|
+
1. Built-in defaults
|
|
175
|
+
2. `~/.config/rho-graph/config.json` (global)
|
|
176
|
+
3. `.rho-graph.json` in the repo root (per-repo)
|
|
177
|
+
4. Environment variables
|
|
178
|
+
|
|
179
|
+
### Full config reference
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
{
|
|
183
|
+
"neo4j": {
|
|
184
|
+
"uri": "bolt://localhost:7687",
|
|
185
|
+
"username": "neo4j",
|
|
186
|
+
"password": "rho-graph",
|
|
187
|
+
"managed": true
|
|
188
|
+
},
|
|
189
|
+
"index": {
|
|
190
|
+
"include": ["**/*"],
|
|
191
|
+
"exclude": ["node_modules", "dist", "vendor", ".git", "build", "__pycache__"],
|
|
192
|
+
"languages": "auto"
|
|
193
|
+
},
|
|
194
|
+
"repos": []
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
`managed: true` means Neo4j is started automatically via Docker Compose. Set to `false` to connect to an existing instance.
|
|
199
|
+
|
|
200
|
+
### Environment variable overrides
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
NEO4J_URI=bolt://my-server:7687
|
|
204
|
+
NEO4J_USERNAME=neo4j
|
|
205
|
+
NEO4J_PASSWORD=secret
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Per-repo config example
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"index": {
|
|
213
|
+
"include": ["src/**/*", "lib/**/*"],
|
|
214
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Bring your own Neo4j
|
|
222
|
+
|
|
223
|
+
If you already have Neo4j running (local, Aura, Docker Compose, etc.):
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"neo4j": {
|
|
228
|
+
"uri": "bolt://localhost:7687",
|
|
229
|
+
"username": "neo4j",
|
|
230
|
+
"password": "your-password",
|
|
231
|
+
"managed": false
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
The Docker Compose file is included in the package if you want to run it manually:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
docker compose -f node_modules/rho-graph/docker-compose.yml up -d
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Automatic reindexing
|
|
245
|
+
|
|
246
|
+
The `install-hook` command writes a `post-commit` git hook that runs `rho-graph index --changed` in the background after every commit. Only files that changed (by content hash) are re-parsed, so the overhead is minimal.
|
|
247
|
+
|
|
248
|
+
If you already have a `post-commit` hook, the command appends to it rather than replacing it.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Development
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
git clone https://github.com/rhofield/rho-graph
|
|
256
|
+
cd rho-graph
|
|
257
|
+
npm install
|
|
258
|
+
|
|
259
|
+
# Start Neo4j
|
|
260
|
+
docker compose up -d
|
|
261
|
+
|
|
262
|
+
# Build
|
|
263
|
+
npm run build
|
|
264
|
+
|
|
265
|
+
# Watch mode
|
|
266
|
+
npm run dev
|
|
267
|
+
|
|
268
|
+
# Tests
|
|
269
|
+
npm test
|
|
270
|
+
|
|
271
|
+
# Type check
|
|
272
|
+
npm run lint
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## License
|
|
276
|
+
|
|
277
|
+
MIT
|
package/bin/rho-graph.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import ora from "ora";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { loadConfig } from "../../config.js";
|
|
4
|
+
import { createConnection } from "../../db/connection.js";
|
|
5
|
+
import { indexRepository } from "../../indexer/index.js";
|
|
6
|
+
export function registerIndexCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command("index")
|
|
9
|
+
.description("Re-index the repository")
|
|
10
|
+
.option("--changed", "Only index changed files")
|
|
11
|
+
.option("--path <path>", "Index a specific path")
|
|
12
|
+
.option("--repo <repoPath>", "Index a different repository")
|
|
13
|
+
.option("--concurrency <n>", "Number of parallel parse tasks", "8")
|
|
14
|
+
.option("--max-memory <mb>", "Max buffer memory in MB before backpressure", "8192")
|
|
15
|
+
.action(async (opts) => {
|
|
16
|
+
const repoPath = resolve(opts.repo || ".");
|
|
17
|
+
const config = loadConfig(repoPath);
|
|
18
|
+
const db = createConnection(config.neo4j);
|
|
19
|
+
const spinner = ora("Parsing files...").start();
|
|
20
|
+
const result = await indexRepository(db, repoPath, config.index, {
|
|
21
|
+
changedOnly: opts.changed,
|
|
22
|
+
specificPath: opts.path,
|
|
23
|
+
concurrency: parseInt(opts.concurrency, 10),
|
|
24
|
+
maxMemoryMB: parseInt(opts.maxMemory, 10),
|
|
25
|
+
onProgress: (current, total) => {
|
|
26
|
+
spinner.text = `Parsing files... ${current}/${total}`;
|
|
27
|
+
},
|
|
28
|
+
onFlushProgress: (completed, total) => {
|
|
29
|
+
spinner.text = `Writing to database... ${completed}/${total} batches`;
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
const orphanSuffix = result.orphansRemoved > 0
|
|
33
|
+
? ` (removed ${result.orphansRemoved} orphaned files)`
|
|
34
|
+
: "";
|
|
35
|
+
spinner.succeed(`Indexed ${result.filesIndexed} files, ${result.functionsFound} functions, ${result.classesFound} classes${orphanSuffix}`);
|
|
36
|
+
if (result.errors.length > 0) {
|
|
37
|
+
console.log(`\n${result.errors.length} files had errors:`);
|
|
38
|
+
for (const err of result.errors.slice(0, 5)) {
|
|
39
|
+
console.log(` ${err.file}: ${err.error}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
await db.close();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=index-cmd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-cmd.js","sourceRoot":"","sources":["../../../src/cli/commands/index-cmd.ts"],"names":[],"mappings":"AAEA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,MAAM,UAAU,oBAAoB,CAAC,OAAgB;IACnD,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,yBAAyB,CAAC;SACtC,MAAM,CAAC,WAAW,EAAE,0BAA0B,CAAC;SAC/C,MAAM,CAAC,eAAe,EAAE,uBAAuB,CAAC;SAChD,MAAM,CAAC,mBAAmB,EAAE,8BAA8B,CAAC;SAC3D,MAAM,CAAC,mBAAmB,EAAE,gCAAgC,EAAE,GAAG,CAAC;SAClE,MAAM,CAAC,mBAAmB,EAAE,6CAA6C,EAAE,MAAM,CAAC;SAClF,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;QACpC,MAAM,EAAE,GAAG,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAE1C,MAAM,OAAO,GAAG,GAAG,CAAC,kBAAkB,CAAC,CAAC,KAAK,EAAE,CAAC;QAChD,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,EAAE;YAC/D,WAAW,EAAE,IAAI,CAAC,OAAO;YACzB,YAAY,EAAE,IAAI,CAAC,IAAI;YACvB,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YAC3C,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YACzC,UAAU,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE;gBAC7B,OAAO,CAAC,IAAI,GAAG,oBAAoB,OAAO,IAAI,KAAK,EAAE,CAAC;YACxD,CAAC;YACD,eAAe,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE;gBACpC,OAAO,CAAC,IAAI,GAAG,0BAA0B,SAAS,IAAI,KAAK,UAAU,CAAC;YACxE,CAAC;SACF,CAAC,CAAC;QACH,MAAM,YAAY,GAChB,MAAM,CAAC,cAAc,GAAG,CAAC;YACvB,CAAC,CAAC,aAAa,MAAM,CAAC,cAAc,kBAAkB;YACtD,CAAC,CAAC,EAAE,CAAC;QACT,OAAO,CAAC,OAAO,CACb,WAAW,MAAM,CAAC,YAAY,WAAW,MAAM,CAAC,cAAc,eAAe,MAAM,CAAC,YAAY,WAAW,YAAY,EAAE,CAC1H,CAAC;QAEF,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,oBAAoB,CAAC,CAAC;YAC3D,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;gBAC5C,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC;QAED,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import ora from "ora";
|
|
2
|
+
import { resolve, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { loadConfig } from "../../config.js";
|
|
5
|
+
import { createConnection } from "../../db/connection.js";
|
|
6
|
+
import { setupSchema } from "../../db/schema.js";
|
|
7
|
+
import { isDockerAvailable, startNeo4j, waitForNeo4j, } from "../../docker/neo4j.js";
|
|
8
|
+
import { indexRepository } from "../../indexer/index.js";
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
export async function runInit() {
|
|
11
|
+
const repoPath = resolve(".");
|
|
12
|
+
const config = loadConfig(repoPath);
|
|
13
|
+
if (config.neo4j.managed) {
|
|
14
|
+
const spinner = ora("Checking prerequisites...").start();
|
|
15
|
+
if (!isDockerAvailable()) {
|
|
16
|
+
spinner.fail("Docker is not available. Install Docker or set neo4j.managed to false.");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
spinner.succeed("Docker available");
|
|
20
|
+
const neo4jSpinner = ora("Starting Neo4j...").start();
|
|
21
|
+
const composePath = resolve(__dirname, "../../../docker-compose.yml");
|
|
22
|
+
startNeo4j(composePath);
|
|
23
|
+
const ready = await waitForNeo4j(config.neo4j.uri, config.neo4j.username, config.neo4j.password);
|
|
24
|
+
if (!ready) {
|
|
25
|
+
neo4jSpinner.fail("Neo4j failed to start");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
neo4jSpinner.succeed("Neo4j running");
|
|
29
|
+
}
|
|
30
|
+
const db = createConnection(config.neo4j);
|
|
31
|
+
const schemaSpinner = ora("Setting up schema...").start();
|
|
32
|
+
await setupSchema(db);
|
|
33
|
+
schemaSpinner.succeed("Schema ready");
|
|
34
|
+
const indexSpinner = ora("Indexing repository...").start();
|
|
35
|
+
const result = await indexRepository(db, repoPath, config.index, {
|
|
36
|
+
onProgress: (current, total) => {
|
|
37
|
+
indexSpinner.text = `Indexing... ${current}/${total}`;
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
indexSpinner.succeed(`Indexed ${result.filesIndexed} files, ${result.functionsFound} functions, ${result.classesFound} classes`);
|
|
41
|
+
if (result.errors.length > 0) {
|
|
42
|
+
console.log(`\n${result.errors.length} files had errors:`);
|
|
43
|
+
for (const err of result.errors.slice(0, 5)) {
|
|
44
|
+
console.log(` ${err.file}: ${err.error}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
await db.close();
|
|
48
|
+
}
|
|
49
|
+
export function registerInitCommand(program) {
|
|
50
|
+
program
|
|
51
|
+
.command("init")
|
|
52
|
+
.description("Initialize Neo4j and index the current repository")
|
|
53
|
+
.action(runInit);
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { writeFileSync, readFileSync, existsSync, chmodSync, } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
const HOOK_CONTENT = `#!/bin/sh
|
|
5
|
+
# code-graph-rag: re-index changed files after commit
|
|
6
|
+
code-graph-rag index --changed &
|
|
7
|
+
`;
|
|
8
|
+
export async function runInstallHook() {
|
|
9
|
+
const spinner = ora("Installing git hook...").start();
|
|
10
|
+
const repoRoot = resolve(".");
|
|
11
|
+
const hookDir = join(repoRoot, ".git", "hooks");
|
|
12
|
+
if (!existsSync(join(repoRoot, ".git"))) {
|
|
13
|
+
spinner.fail("Not a git repository");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const hookPath = join(hookDir, "post-commit");
|
|
17
|
+
if (existsSync(hookPath)) {
|
|
18
|
+
const existing = readFileSync(hookPath, "utf-8");
|
|
19
|
+
if (existing.includes("code-graph-rag")) {
|
|
20
|
+
spinner.succeed("Hook already installed");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
writeFileSync(hookPath, existing + "\n" + HOOK_CONTENT);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
writeFileSync(hookPath, HOOK_CONTENT);
|
|
27
|
+
}
|
|
28
|
+
chmodSync(hookPath, 0o755);
|
|
29
|
+
spinner.succeed(`Hook installed at ${hookPath}`);
|
|
30
|
+
}
|
|
31
|
+
export function registerInstallHookCommand(program) {
|
|
32
|
+
program
|
|
33
|
+
.command("install-hook")
|
|
34
|
+
.description("Install git post-commit hook for automatic re-indexing")
|
|
35
|
+
.action(runInstallHook);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=install-hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-hook.js","sourceRoot":"","sources":["../../../src/cli/commands/install-hook.ts"],"names":[],"mappings":"AACA,OAAO,EACL,aAAa,EACb,YAAY,EACZ,UAAU,EACV,SAAS,GACV,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,GAAG,MAAM,KAAK,CAAC;AAEtB,MAAM,YAAY,GAAG;;;CAGpB,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,OAAO,GAAG,GAAG,CAAC,wBAAwB,CAAC,CAAC,KAAK,EAAE,CAAC;IACtD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAEhD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;QACxC,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IAE9C,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACjD,IAAI,QAAQ,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACxC,OAAO,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC;YAC1C,OAAO;QACT,CAAC;QACD,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,GAAG,YAAY,CAAC,CAAC;IAC1D,CAAC;SAAM,CAAC;QACN,aAAa,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IACxC,CAAC;IAED,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC3B,OAAO,CAAC,OAAO,CAAC,qBAAqB,QAAQ,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,OAAgB;IACzD,OAAO;SACJ,OAAO,CAAC,cAAc,CAAC;SACvB,WAAW,CAAC,wDAAwD,CAAC;SACrE,MAAM,CAAC,cAAc,CAAC,CAAC;AAC5B,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
export async function runInstallMcp() {
|
|
6
|
+
const spinner = ora("Installing MCP server...").start();
|
|
7
|
+
const claudeDir = join(homedir(), ".claude");
|
|
8
|
+
const settingsPath = join(claudeDir, "settings.json");
|
|
9
|
+
if (!existsSync(claudeDir)) {
|
|
10
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
let settings = {};
|
|
13
|
+
if (existsSync(settingsPath)) {
|
|
14
|
+
settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
15
|
+
}
|
|
16
|
+
const mcpServers = settings.mcpServers || {};
|
|
17
|
+
mcpServers["code-graph-rag"] = {
|
|
18
|
+
command: "npx",
|
|
19
|
+
args: ["code-graph-rag", "mcp-serve"],
|
|
20
|
+
type: "stdio",
|
|
21
|
+
};
|
|
22
|
+
settings.mcpServers = mcpServers;
|
|
23
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
24
|
+
spinner.succeed("MCP server registered in ~/.claude/settings.json");
|
|
25
|
+
}
|
|
26
|
+
export function registerInstallMcpCommand(program) {
|
|
27
|
+
program
|
|
28
|
+
.command("install-mcp")
|
|
29
|
+
.description("Register the MCP server in Claude Code settings")
|
|
30
|
+
.action(runInstallMcp);
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=install-mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-mcp.js","sourceRoot":"","sources":["../../../src/cli/commands/install-mcp.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,GAAG,MAAM,KAAK,CAAC;AAEtB,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,OAAO,GAAG,GAAG,CAAC,0BAA0B,CAAC,CAAC,KAAK,EAAE,CAAC;IAExD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IAC7C,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;IAEtD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,IAAI,QAAQ,GAA4B,EAAE,CAAC;IAC3C,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC7B,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,MAAM,UAAU,GACb,QAAQ,CAAC,UAAsC,IAAI,EAAE,CAAC;IACzD,UAAU,CAAC,gBAAgB,CAAC,GAAG;QAC7B,OAAO,EAAE,KAAK;QACd,IAAI,EAAE,CAAC,gBAAgB,EAAE,WAAW,CAAC;QACrC,IAAI,EAAE,OAAO;KACd,CAAC;IACF,QAAQ,CAAC,UAAU,GAAG,UAAU,CAAC;IAEjC,aAAa,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/D,OAAO,CAAC,OAAO,CAAC,kDAAkD,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,OAAgB;IACxD,OAAO;SACJ,OAAO,CAAC,aAAa,CAAC;SACtB,WAAW,CAAC,iDAAiD,CAAC;SAC9D,MAAM,CAAC,aAAa,CAAC,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import { loadConfig } from "../../config.js";
|
|
4
|
+
import { createConnection } from "../../db/connection.js";
|
|
5
|
+
export function registerQueryCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command("query [cypher]")
|
|
8
|
+
.description("Run Cypher queries against the graph (interactive REPL if no query given)")
|
|
9
|
+
.option("--callers <functionName>", "Find all callers of a function")
|
|
10
|
+
.option("--dependencies <filePath>", "Find dependencies of a file")
|
|
11
|
+
.option("--structure", "Show high-level repo structure")
|
|
12
|
+
.action(async (cypher, opts) => {
|
|
13
|
+
const repoPath = resolve(".");
|
|
14
|
+
const config = loadConfig(repoPath);
|
|
15
|
+
const db = createConnection(config.neo4j);
|
|
16
|
+
const healthy = await db.healthCheck();
|
|
17
|
+
if (!healthy) {
|
|
18
|
+
console.log("Neo4j is not running. Run `code-graph-rag init` to start it.");
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
if (opts.callers) {
|
|
22
|
+
cypher =
|
|
23
|
+
"MATCH (caller:Function)-[:CALLS]->(callee:Function {name: $name}) RETURN caller.name AS caller, caller.filePath AS file, caller.startLine AS line";
|
|
24
|
+
}
|
|
25
|
+
else if (opts.dependencies) {
|
|
26
|
+
cypher =
|
|
27
|
+
"MATCH (f:File)-[:IMPORTS]->(dep:File) WHERE f.relativePath = $path OR f.path ENDS WITH $path RETURN dep.relativePath AS dependency";
|
|
28
|
+
}
|
|
29
|
+
else if (opts.structure) {
|
|
30
|
+
cypher =
|
|
31
|
+
"MATCH (r:Repository)-[:CONTAINS_FILE]->(f:File) RETURN r.name AS repo, f.language AS language, count(f) AS files ORDER BY files DESC";
|
|
32
|
+
}
|
|
33
|
+
if (cypher) {
|
|
34
|
+
const params = {};
|
|
35
|
+
if (opts.callers)
|
|
36
|
+
params.name = opts.callers;
|
|
37
|
+
if (opts.dependencies)
|
|
38
|
+
params.path = opts.dependencies;
|
|
39
|
+
await runQuery(db, cypher, params);
|
|
40
|
+
await db.close();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// Interactive REPL
|
|
44
|
+
console.log("code-graph-rag query REPL (type 'exit' to quit)\n");
|
|
45
|
+
const rl = createInterface({
|
|
46
|
+
input: process.stdin,
|
|
47
|
+
output: process.stdout,
|
|
48
|
+
prompt: "cypher> ",
|
|
49
|
+
});
|
|
50
|
+
rl.prompt();
|
|
51
|
+
rl.on("line", async (line) => {
|
|
52
|
+
const trimmed = line.trim();
|
|
53
|
+
if (trimmed === "exit" || trimmed === "quit") {
|
|
54
|
+
await db.close();
|
|
55
|
+
rl.close();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (trimmed) {
|
|
59
|
+
await runQuery(db, trimmed, {});
|
|
60
|
+
}
|
|
61
|
+
rl.prompt();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async function runQuery(db, cypher, params) {
|
|
66
|
+
const session = db.session();
|
|
67
|
+
try {
|
|
68
|
+
const result = await session.run(cypher, params);
|
|
69
|
+
if (result.records.length === 0) {
|
|
70
|
+
console.log("(no results)");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const keys = result.records[0].keys;
|
|
74
|
+
console.log(keys.join("\t"));
|
|
75
|
+
console.log(keys.map(() => "---").join("\t"));
|
|
76
|
+
for (const record of result.records) {
|
|
77
|
+
const values = keys.map((k) => {
|
|
78
|
+
const v = record.get(k);
|
|
79
|
+
return typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
80
|
+
});
|
|
81
|
+
console.log(values.join("\t"));
|
|
82
|
+
}
|
|
83
|
+
console.log(`\n${result.records.length} row(s)`);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.error(`Query error: ${error instanceof Error ? error.message : error}`);
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
await session.close();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../../src/cli/commands/query.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { runInit } from "./init.js";
|
|
2
|
+
import { runInstallMcp } from "./install-mcp.js";
|
|
3
|
+
import { runInstallHook } from "./install-hook.js";
|
|
4
|
+
export function registerSetupCommand(parent) {
|
|
5
|
+
parent
|
|
6
|
+
.command("setup")
|
|
7
|
+
.description("Full setup: init + install MCP server + install git hook")
|
|
8
|
+
.action(async () => {
|
|
9
|
+
await runInit();
|
|
10
|
+
await runInstallMcp();
|
|
11
|
+
await runInstallHook();
|
|
12
|
+
console.log("\nReady! Your AI agent now has access to graph-powered code search.");
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../src/cli/commands/setup.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,UAAU,oBAAoB,CAAC,MAAe;IAClD,MAAM;SACH,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,0DAA0D,CAAC;SACvE,MAAM,CAAC,KAAK,IAAI,EAAE;QACjB,MAAM,OAAO,EAAE,CAAC;QAChB,MAAM,aAAa,EAAE,CAAC;QACtB,MAAM,cAAc,EAAE,CAAC;QAEvB,OAAO,CAAC,GAAG,CACT,qEAAqE,CACtE,CAAC;IACJ,CAAC,CAAC,CAAC;AACP,CAAC"}
|