🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

geraph

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

geraph - npm Package Compare versions

Comparing version
0.1.0
to
0.2.0
+159
dist/query-ALVJVDRK.js
// src/core/query.ts
import fs from "fs";
import path from "path";
import { MultiDirectedGraph } from "graphology";
function normalizeId(id) {
return id.replace(/\\/g, "/");
}
function loadGraph(targetDir) {
const graphPath = path.join(targetDir, ".geraph", "graph.json");
if (!fs.existsSync(graphPath)) {
throw new Error("Graph data not found. Run 'geraph scan' first.");
}
const rawData = JSON.parse(fs.readFileSync(graphPath, "utf-8"));
const graph = new MultiDirectedGraph();
const nodes = rawData.nodes || [];
const edges = rawData.edges || [];
nodes.forEach((n) => {
const nid = normalizeId(n.id);
if (!graph.hasNode(nid)) {
const { id, name, type, file, startLine, ...metadata } = n;
graph.addNode(nid, {
name: name || "",
type,
file: normalizeId(file || ""),
startLine: startLine || 0,
metadata: metadata || {}
});
}
});
edges.forEach((e) => {
const source = normalizeId(e.source);
const target = normalizeId(e.target);
if (graph.hasNode(source) && graph.hasNode(target)) {
graph.addEdge(source, target, {
type: e.relation || e.type,
confidence: e.confidence,
metadata: e.metadata || {}
});
}
});
return graph;
}
async function searchGraph(targetDir, term, targetType) {
const graph = loadGraph(targetDir);
const lowerTerm = term.toLowerCase();
const results = [];
graph.forEachNode((nodeId, attr) => {
if (targetType && attr.type !== targetType) return;
if (nodeId.toLowerCase().includes(lowerTerm) || attr.name && attr.name.toLowerCase().includes(lowerTerm)) {
results.push({
id: nodeId,
name: attr.name,
type: attr.type,
file: attr.file,
links: graph.degree(nodeId)
});
}
});
return results.sort((a, b) => b.links - a.links);
}
async function queryGraph(targetDir, symbol, targetType, targetSource) {
const graph = loadGraph(targetDir);
const normSymbol = normalizeId(symbol);
let targetNodeId = graph.hasNode(normSymbol) ? normSymbol : null;
if (targetNodeId && (targetType || targetSource)) {
const attr = graph.getNodeAttributes(targetNodeId);
if (targetType && attr.type !== targetType) {
targetNodeId = null;
}
if (targetNodeId && targetSource) {
const normSource = normalizeId(targetSource);
if (!attr.file.toLowerCase().endsWith(normSource.toLowerCase())) {
targetNodeId = null;
}
}
}
if (!targetNodeId) {
targetNodeId = graph.findNode((nodeId, attr) => {
if (targetType && attr.type !== targetType) return false;
if (targetSource) {
const normSource = normalizeId(targetSource);
if (!attr.file.endsWith(normSource)) return false;
}
return attr && attr.name && attr.name === symbol || nodeId === normSymbol || nodeId.endsWith("/" + normSymbol) || nodeId.endsWith("::" + normSymbol);
}) ?? null;
}
if (!targetNodeId) {
targetNodeId = graph.findNode((nodeId, attr) => {
if (targetType && attr.type !== targetType) return false;
if (targetSource) {
const normSource = normalizeId(targetSource);
if (!attr.file.toLowerCase().endsWith(normSource.toLowerCase())) return false;
}
return attr && attr.name && attr.name.toLowerCase() === symbol.toLowerCase() || nodeId.toLowerCase() === normSymbol.toLowerCase() || nodeId.toLowerCase().endsWith("/" + normSymbol.toLowerCase()) || nodeId.toLowerCase().endsWith("::" + normSymbol.toLowerCase());
}) ?? null;
}
if (!targetNodeId) {
const typeMsg = targetType ? ` of type '${targetType}'` : "";
const sourceMsg = targetSource ? ` in source '${targetSource}'` : "";
throw new Error(`Symbol '${symbol}'${typeMsg}${sourceMsg} not found in the graph.`);
}
const targetAttr = graph.getNodeAttributes(targetNodeId);
const result = {
target: {
id: targetNodeId,
name: targetAttr.name,
type: targetAttr.type,
file: targetAttr.file,
line: targetAttr.startLine,
metadata: targetAttr.metadata,
links: {
incoming: graph.inDegree(targetNodeId),
outgoing: graph.outDegree(targetNodeId)
}
},
incoming: [],
outgoing: []
};
const collectEdges = (nodeId, isOutgoing, seenKeys) => {
const iterator = isOutgoing ? graph.forEachOutEdge.bind(graph) : graph.forEachInEdge.bind(graph);
iterator(nodeId, (edge, attr, source, target) => {
const neighborId = isOutgoing ? target : source;
if (neighborId === targetNodeId) return;
const key = `${neighborId}:${attr.type}`;
if (seenKeys.has(key)) return;
seenKeys.add(key);
const neighborAttr = graph.getNodeAttributes(neighborId);
const nodeInfo = {
id: neighborId,
name: neighborAttr.name,
type: neighborAttr.type,
file: neighborAttr.file,
line: neighborAttr.startLine
};
if (isOutgoing) {
result.outgoing.push({
relation: attr.type,
confidence: attr.confidence,
target: nodeInfo
});
} else {
result.incoming.push({
relation: attr.type,
confidence: attr.confidence,
source: nodeInfo
});
}
});
};
const seenIn = /* @__PURE__ */ new Set();
const seenOut = /* @__PURE__ */ new Set();
collectEdges(targetNodeId, false, seenIn);
collectEdges(targetNodeId, true, seenOut);
return result;
}
export {
queryGraph,
searchGraph
};
+94
-50

@@ -531,3 +531,6 @@ #!/usr/bin/env node

`;
const fileNodes = graph.nodes().filter((n) => graph.getNodeAttribute(n, "type") === "file");
md += `*Note: Only the first 100 files and their primary members are listed here. Use 'query' for full details.*
`;
const fileNodes = graph.nodes().filter((n) => graph.getNodeAttribute(n, "type") === "file").slice(0, 100);
for (const file of fileNodes) {

@@ -541,3 +544,4 @@ const data = graph.getNodeAttributes(file);

});
for (const edgeId of definesEdges) {
const membersToShow = definesEdges.slice(0, 5);
for (const edgeId of membersToShow) {
const target = graph.target(edgeId);

@@ -548,2 +552,6 @@ const targetData = graph.getNodeAttributes(target);

}
if (definesEdges.length > 5) {
md += ` - ... and ${definesEdges.length - 5} more
`;
}
}

@@ -619,3 +627,9 @@ if (analysis && analysis.godNodes.length > 0) {

`;
const intentNodes = graph.nodes().filter((n) => graph.getNodeAttribute(n, "type") === "intent");
const intentNodes = graph.nodes().filter((n) => graph.getNodeAttribute(n, "type") === "intent").sort((a, b) => {
const metaA = graph.getNodeAttribute(a, "metadata");
const metaB = graph.getNodeAttribute(b, "metadata");
const dateA = metaA?.date || "";
const dateB = metaB?.date || "";
return dateB.localeCompare(dateA);
}).slice(0, 50);
if (intentNodes.length > 0) {

@@ -625,5 +639,8 @@ md += `

`;
md += `*Showing the 50 most recent architectural commits. Use 'query' on a specific symbol to see its full history.*
`;
for (const intent of intentNodes) {
const data = graph.getNodeAttributes(intent);
const msg = data.metadata?.message || "";
const msg = (data.metadata?.message || "").split("\n")[0];
md += `- **${data.name}**: ${msg}

@@ -940,6 +957,12 @@ `;

Rules:
- If running any \`geraph\` command returns 'command not found', you MUST install it globally using \`npm install -g geraph\` before proceeding.
- Before answering architecture or codebase questions, read \`.geraph/GRAPH_REPORT.md\` for god nodes and community structure.
- NEVER try to read the massive \`.geraph/graph.json\` file directly. It is for internal CLI use only.
- If the geraph CLI is available, utilize \`npx geraph query "<symbol>"\` for precise architecture navigation instead of falling back to \`grep\`.
- After modifying code files in this session, run \`npx geraph scan\` to keep the graph current.
- For discovery, use \`geraph search '<term>' [--type <type>]\`.
- For precise architecture navigation, use \`geraph query '<symbol>' [--type <type>] [--source <file>]\` instead of falling back to \`grep\`.
- After modifying code files in this session, run \`geraph scan\` to keep the graph current.
- NEVER use \`grep\`, \`rg\`, \`find\`, or any text-search tool for architecture questions when geraph is available. Always use \`geraph search\` or \`geraph query\` instead.
- **NOTE**: There are NO extra or hidden options for these commands. DO NOT hallucinate flags. Only use the options explicitly mentioned here.
[GLOBAL_POINTER]
`;

@@ -954,16 +977,14 @@ var COPILOT_RULES = `## geraph

NEVER attempt to read the massive \`.geraph/graph.json\` file directly. Use the CLI instead.
Use \`npx geraph query "<symbol>"\` to trace dependencies, and \`npx geraph scan\` to update the graph after writing code.
`;
var CURSOR_RULE = `---
description: geraph codebase knowledge graph
alwaysApply: true
---
If running any \`geraph\` command returns a 'command not found' error, you MUST install it globally using \`npm install -g geraph\` before proceeding.
This project uses a geraph knowledge graph.
NEVER attempt to read the massive \`.geraph/graph.json\` file directly. Use the CLI instead:
- **Discover nodes**: \`geraph search '<term>' [--type <type>]\`
- **Trace dependencies**: \`geraph query '<symbol>' [--type <type>] [--source <file>]\`
- **Update graph**: \`geraph scan\`
- Before answering architecture or codebase questions, read .geraph/GRAPH_REPORT.md for god nodes and community structure.
- NEVER try to read the massive \`.geraph/graph.json\` file directly. It is for internal CLI use only.
- For cross-module "how does X relate to Y" questions or dependency tracing, prefer \`npx geraph query "<symbol>"\` over grep \u2014 it guarantees precision.
- After modifying code files in this session, run \`npx geraph scan\` to keep the graph current.
NEVER use \`grep\`, \`rg\`, \`find\`, or any text-search tool for architecture questions when geraph is available. Always use \`geraph search\` or \`geraph query\` instead.
**NOTE**: There are NO extra or hidden options for these commands. DO NOT hallucinate flags. Only use the options explicitly mentioned here.
[GLOBAL_POINTER]
`;

@@ -977,3 +998,3 @@ var ANTIGRAVITY_WORKFLOW = `---

Follow the geraph skill installed at ~/.agent/skills/geraph/SKILL.md to run queries.
[GLOBAL_POINTER]
`;

@@ -997,3 +1018,8 @@ var PLATFORMS = {

path: ".cursor/rules/geraph.mdc",
content: CURSOR_RULE,
content: `---
description: geraph codebase knowledge graph
alwaysApply: true
---
[GERAPH_SKILL_CONTENT_PLACEHOLDER]`,
inject: false

@@ -1088,6 +1114,19 @@ }

}
let fileContentToInject = localFile.content || skillContent;
fileContentToInject = fileContentToInject.replace(
"[GERAPH_SKILL_CONTENT_PLACEHOLDER]",
skillContent
);
if (platform.globalPath) {
fileContentToInject = fileContentToInject.replace(
"[GLOBAL_POINTER]",
`Before answering, you MUST read the [Geraph Skill](${platform.globalPath}) operational manual.`
);
} else {
fileContentToInject = fileContentToInject.replace("[GLOBAL_POINTER]", "");
}
if (localFile.inject) {
const injection = `
${START_MARKER}
${(localFile.content || skillContent).trim()}
${fileContentToInject.trim()}
${END_MARKER}

@@ -1112,3 +1151,3 @@ `;

} else {
fs4.writeFileSync(fullPath, localFile.content || "");
fs4.writeFileSync(fullPath, fileContentToInject);
results.push(`${localFile.path} created/updated`);

@@ -1317,23 +1356,5 @@ }

console.log(chalk3.dim(` - Time: ${durationSeconds}s`));
if (analysis.godNodes.length > 0) {
console.log();
console.log(chalk3.bold("God Nodes (architectural pillars):"));
for (const god of analysis.godNodes.slice(0, 5)) {
console.log(
chalk3.yellow(
` \u2605 ${god.name} (${god.type}, ${god.degree} connections)`
)
);
}
}
if (analysis.surprisingConnections.length > 0) {
console.log();
console.log(chalk3.bold("Surprising Connections:"));
for (const s of analysis.surprisingConnections.slice(0, 3)) {
console.log(
chalk3.magenta(` \u26A1 ${s.sourceName} \u2194 ${s.targetName}: ${s.why}`)
);
}
}
console.log();
console.log(chalk3.cyan(`Type '/geraph' in your AI chat to begin.`));
console.log();
} catch (error) {

@@ -1361,3 +1382,3 @@ spinner.fail(chalk3.red("Failed to scan directory."));

chalk3.gray(
` Run 'npx geraph install' to install the default AGENTS.md for basic LLM support.
` Run 'geraph install' to install the default AGENTS.md for basic LLM support.
`

@@ -1391,3 +1412,3 @@ )

chalk3.cyan(
"Next Step: Run 'npx geraph scan' to build the graphical knowledge base."
"Next Step: Run 'geraph scan' to build the graphical knowledge base."
)

@@ -1449,6 +1470,30 @@ );

});
program.command("query <symbol>").description(
"Query the knowledge graph for a specific symbol's relationships"
).action(async (symbol) => {
program.command("search <term>").description("Discover multiple nodes matching a partial term").option("-t, --type <type>", "Filter results by node type (e.g., 'interface', 'class', 'function', 'file')").action(async (term, options) => {
const spinner = ora({
text: chalk3.gray(`Searching graph for: ${term}...`),
color: "blue",
spinner: "dots"
}).start();
try {
const { searchGraph } = await import("./query-ALVJVDRK.js");
const results = await searchGraph(process.cwd(), term, options.type);
spinner.stop();
if (results.length === 0) {
console.error(chalk3.yellow(`No nodes found matching '${term}'`));
} else {
console.log(JSON.stringify(results, null, 2));
console.error(chalk3.gray(`
Found ${results.length} nodes. Use 'geraph query <id>' to inspect a specific node.`));
}
} catch (error) {
spinner.stop();
console.error(
chalk3.red("?? Search failed:"),
error instanceof Error ? error.message : String(error)
);
process.exit(1);
}
});
program.command("query <symbol>").description("Query the knowledge graph for a specific symbol's relationships").option("-t, --type <type>", "Filter results by node type (e.g., 'interface', 'class', 'function', 'file')").option("-s, --source <path>", "Filter results by source file path (e.g., 'auth.ts')").action(async (symbol, options) => {
const spinner = ora({
text: chalk3.gray(`Querying relationships for: ${symbol}...`),

@@ -1459,7 +1504,6 @@ color: "blue",

try {
const { queryGraph } = await import("./query-KYZCIKSV.js");
const result = await queryGraph(process.cwd(), symbol);
const { queryGraph } = await import("./query-ALVJVDRK.js");
const result = await queryGraph(process.cwd(), symbol, options.type, options.source);
spinner.stop();
console.log(JSON.stringify(result, null, 2));
console.log();
} catch (error) {

@@ -1466,0 +1510,0 @@ spinner.fail(

@@ -15,64 +15,79 @@ ---

### Key Outputs
- **`.geraph/GRAPH_REPORT.md`**: A human-readable architectural summary containing project stats, core architectural pillars ("God Nodes"), and community clustering. **Always read this file first** to understand the high-level architecture before answering complex questions.
- **`.geraph/graph.json`**: The raw serialized graph data used by the CLI.
- **`.geraph/GRAPH_REPORT.md`**: A human-readable architectural summary containing project stats, core architectural pillars ("God Nodes"), and community clustering. **Always read this file first** to understand the high-level architecture. Note that for large projects, this report is truncated to the "Top 100" files and "Top 50" recent commits. Use the `query` command for surgical precision and complete history.
- **`.geraph/graph.html`**: An interactive 2D/3D visualization of the codebase. Useful for humans to spot clusters and flow, but not for AI agents.
- **`.geraph/graph.json`**: The raw serialized graph data. **CRITICAL**: NEVER attempt to read this file directly as it can be massive and will overflow your context window. Always use the `query` command to fetch filtered, token-efficient data.
## Operational Protocol
### 1. Tracing Dependencies (The Query Command)
**CRITICAL RULE**: NEVER attempt to read `.geraph/graph.json` or `.geraph/graph.html` directly. These are massive serialized files. You MUST use the CLI `query` command to fetch crisp, lightweight results.
- **CRITICAL RULE**: NEVER read `.geraph/graph.json` or `.geraph/graph.html` directly. Always use the CLI.
- **COMMAND NOT FOUND**: If the terminal returns a 'command not found' error for any `geraph` command, you MUST install it globally using `npm install -g geraph` before proceeding.
- **TERMINAL OUTPUT BUG**: If your agent environment/terminal fails to capture stdout (e.g., it returns "No output" or the command silently hangs), **DO NOT GIVE UP.** Run the command again and redirect the output to a file inside the `.geraph` directory: `geraph query 'Symbol' > .geraph/out.json`, then read that file. NEVER write output files to the project root.
- **CRITICAL RULE**: DO NOT use `| head` or `| grep`. The CLI is already token-optimized and piping hides crucial error messages.
- **MANDATORY**: You MUST wait for the terminal response after running any Geraph command. Do not hallucinate results.
If you need to know who calls a function, what dependencies a file has, or what a symbol does, use the CLI.
**Syntax**: `npx geraph query '<symbol>'`
- **Mandatory Quoting**: Always wrap the query in single quotes to prevent terminal expansion.
- **Search by Name First**: E.g., `npx geraph query 'saveCache'`.
- **Search by ID Next**: The initial query will return unique IDs (e.g., `src/core/git.ts::saveCache`). For 100% surgical precision in subsequent lookups, query the exact ID.
- **Empty Results**: If a query returns nothing, the symbol does not exist in the scanned scope. Check for typos or try querying the file name instead.
### Command Reference
#### Understanding Query Results
The `query` command outputs a JSON object with three main keys:
1. **`target`**: The node you searched for. Pay special attention to the `metadata` object inside it.
- `metadata.doc`: The full JSDoc block comment for the node. **ALWAYS read this** to understand the "Why" and the intended usage of the function/class.
- `metadata.deprecated`: If `true`, NEVER use or recommend this node in new code.
- `line` & `metadata.endLine`: The exact line range of the definition.
- `links`: The total count of `incoming` and `outgoing` relationships this node has in the full graph. High counts indicate "Hub" or "God" nodes.
2. **`incoming`**: Nodes that depend on the target (e.g., functions that call it, or files that import it).
3. **`outgoing`**: Nodes that the target depends on (e.g., what functions it calls internally).
| Command | Syntax | When to Use |
|---|---|---|
| **Search** | `geraph search '<term>' [--type <type>]` | For broad/fuzzy discovery. Use when you only know part of a name or want to see all nodes matching a concept (e.g., `search 'auth'`). Returns a lightweight array of matching IDs. |
| **Query** | `geraph query '<symbol>' [--type <type>] [--source <file>]` | For deep inspection. Use when you know the exact ID or exact name of a symbol. Returns full dependencies (`incoming`/`outgoing`) and metadata. |
| **Scan** | `geraph scan` | Run this IMMEDIATELY after you make any type of change in the codebase to ensure the graph is up to date. |
### 2. After Modifying Code (The Scan Command)
Geraph tracks the codebase statically. If you add, delete, or rename files, functions, or classes, the graph will become out of date.
**Syntax**: `npx geraph scan`
- **When to run**: IMMEDIATELY after you complete any structural modifications or refactoring in the current session. This ensures subsequent queries are accurate.
*Note on Flags: All command options/flags are optional, but it is highly recommended to use them if you know the exact type or source, as it guarantees precise results. `--type` and `--source` are the ONLY valid flags. NEVER invent flags like `--limit` or `--dfs`.*
## Standard Workflows
### JSON Response Schema
When you run a command, it returns pure JSON on stdout. Here is how to interpret the fields:
**Scenario A: "What does this function do?"**
- Run `npx geraph query '<function_name>'`.
- Analyze `outgoing` connections to see its internal dependencies.
- Analyze `incoming` connections to see where it is used.
- Use the `file` and `line` metadata in the output to directly read the implementation.
**`search` output**: Returns an array of matching node objects, sorted by connection count (most connected first). Each object contains:
- `id`: The unique node identifier (format: `filePath::symbolName` for code symbols, `commit::hash` for intents, or a raw file path for files).
- `name`: The human-readable name of the symbol.
- `type`: The node type (e.g., `function`, `class`, `interface`, `file`, `intent`).
- `file`: The source file where this node is defined.
- `links`: Total number of connections (incoming + outgoing). Higher means more architecturally significant.
**Scenario B: "Change this component"**
- Query the component.
- Analyze `incoming` edges to identify all dependents. **You must ensure your changes do not break these callers.**
**`query` output**: Returns a detailed object with `target`, `incoming`, and `outgoing`:
- `target`: The queried node's full details:
- `id`, `name`, `type`, `file`, `line`: Identity and location.
- `metadata.doc`: Contains extracted JSDoc/comments. Read this to understand the purpose and intent of the symbol.
- `metadata.deprecated`: Boolean flag. If `true`, this symbol is marked `@deprecated`.
- `metadata.message`: (*Only on `intent` type nodes*) The Git commit message explaining why this node was created or changed.
- `metadata.author`, `metadata.date`: (*Only on `intent` type nodes*) Commit author and timestamp.
- `links.incoming` / `links.outgoing`: Count of connections in each direction.
- `incoming`: Array of edges pointing **to** this node. Each entry has `source` (the neighbor node), `relation` (edge type), and `confidence`. Use this for **Impact Analysis** — these are the entities that depend on and will break if you change the target.
- `outgoing`: Array of edges pointing **out** from this node. Each entry has `target` (the neighbor node), `relation`, and `confidence`. Use this to see what the node **depends on** — what it calls, imports, or references.
## Geraph Glossary
### Query Resolution Priority
When you `query` a symbol name (e.g., `geraph query 'userState'`), Geraph resolves it in this strict order:
1. **Exact ID Match**: Perfect match on the unique Node ID.
2. **Case-Sensitive Match**: Matches the exact capitalization (finds the variable `userState` but ignores the interface `UserState`).
3. **Case-Insensitive Fallback**: If no exact case match exists, it returns the case-insensitive match (returns the interface `UserState`).
### Node Types
- `file`: A source code file.
- `function`: A standard function/method.
- `class`: A class definition.
- `interface` / `type` / `enum`: TypeScript type definitions.
- `[script] filename.ts`: The top-level execution block of a file (code outside any function/class). Query this to see what a file does upon import/execution.
- `intent`: A Git commit explaining why a node exists.
### Standard Workflows
### Edge Types
- `imports`: File A depends on File B.
- `calls`: Function A executes Function B.
- `defines`: A file contains a function/class.
- `references`: A function uses a specific type.
- `explains`: A Git commit provides historical context for a node.
| Scenario | Action / Command | Why |
|---|---|---|
| **"How does [Concept] work?"** | 1. Read `GRAPH_REPORT.md` to find God Nodes.<br>2. `geraph search '<concept>'` | Geraph is an AST graph. It does not understand English words like 'auth' or 'database'. You MUST use `search` first to find the actual code symbols (e.g. `authSlice.ts`), and then `query` those symbols. NEVER `query` a raw concept. |
| **User mentions a file (e.g. @file:xyz.ts)** | `geraph query '<filepath>' --type file` | ALWAYS query a mentioned file first. Analyzing its `outgoing` connections instantly reveals all classes/functions defined inside it, so you don't have to guess symbol names. |
| **"What does this function do?"**| `geraph query '<funcName>' --type function` | Read `target.metadata.doc` for intent. Look at `outgoing` for what it calls, and `incoming` for who calls it. |
| **"Impact of changing a field/property?"** | `geraph query '<ContainerName>'` | Geraph DOES NOT index individual fields (like `avatar`). You MUST query the Interface/Class that contains the field (e.g., `UserState`), then analyze its `incoming` edges. NEVER query the field name directly. |
| **"Impact of changing a class/function?"** | `geraph query '<symbolName>'` | Analyze the `incoming` array. These are the exact entities that depend on your target and might break. |
| **"Query Failed / Not Found"** | `geraph search '<symbolName>'` | Do NOT fallback to `grep`. If the terminal fails to capture output, redirect to a file (`> .geraph/out.json`) and read it. If it returns a genuine "Not found" error, use the `geraph search` command to find the correct naming. |
### Confidence Levels
- `EXTRACTED`: Perfect confidence (100%).
- `INFERRED`: High confidence (heuristics).
- `AMBIGUOUS`: Moderate confidence (dynamic calls/overlapping names).
### Geraph Glossary
| Node Type | Description |
|---|---|
| `file` | A source code file. |
| `function` | A standard function/method. |
| `class` | A class definition. |
| `interface`/`type`/`enum`| TypeScript type definitions. |
| `[script] <name>` | The top-level execution block of a file (code outside any function/class). |
| `intent` | A Git commit explaining why a node exists. |
| Edge Type | Description |
|---|---|
| `imports` | File A depends on File B. |
| `calls` | Function A executes Function B. |
| `defines` | A file contains a function/class. |
| `references` | A function uses a specific type. |
| `explains` | A Git commit provides historical context for a node. |
{
"name": "geraph",
"version": "0.1.0",
"version": "0.2.0",
"description": "Structural memory for AI agents. Build semantic knowledge graphs from your codebase for surgical code modifications.",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"bin": {
"geraph": "./dist/index.js"
"geraph": "dist/index.js"
},

@@ -30,3 +30,3 @@ "scripts": {

"type": "git",
"url": "https://github.com/rupam2232/geraph"
"url": "git+https://github.com/rupam2232/geraph.git"
},

@@ -33,0 +33,0 @@ "publishConfig": {

@@ -9,10 +9,13 @@ # Geraph

The fastest way to use Geraph is via `npx`:
Run the following commands to install geraph cli, platform specific rules and build the graph:
```bash
# 1. Setup your favorite AI assistant
npx geraph install claude # or antigravity, vscode, cursor
# 1. Install globally
npm install -g geraph
# 2. Map your project
npx geraph scan
# 2. Setup your favorite AI assistant
geraph install claude # or antigravity, vscode, cursor
# 3. Map your project
geraph scan
```

@@ -19,0 +22,0 @@

// src/core/query.ts
import fs from "fs";
import path from "path";
import { MultiDirectedGraph } from "graphology";
function normalizeId(id) {
return id.replace(/\\/g, "/");
}
async function queryGraph(targetDir, symbol) {
const graphPath = path.join(targetDir, ".geraph", "graph.json");
if (!fs.existsSync(graphPath)) {
throw new Error("Graph data not found. Run 'npx geraph scan' first.");
}
const rawData = JSON.parse(fs.readFileSync(graphPath, "utf-8"));
const graph = new MultiDirectedGraph();
const nodes = rawData.nodes || [];
const edges = rawData.edges || [];
nodes.forEach((n) => {
const nid = normalizeId(n.id);
if (!graph.hasNode(nid)) {
const { name, type, file, startLine, ...metadata } = n;
graph.addNode(nid, {
name: name || "",
type,
file: file || "",
startLine: startLine || 0,
metadata: metadata || {}
});
}
});
edges.forEach((e) => {
const source = normalizeId(e.source);
const target = normalizeId(e.target);
if (graph.hasNode(source) && graph.hasNode(target)) {
graph.addEdge(source, target, {
type: e.relation || e.type,
confidence: e.confidence,
metadata: e.metadata || {}
});
}
});
const normSymbol = normalizeId(symbol);
let targetNodeId = graph.hasNode(normSymbol) ? normSymbol : null;
if (!targetNodeId) {
targetNodeId = graph.findNode(
(nodeId, attr) => attr && attr.name && attr.name.toLowerCase() === symbol.toLowerCase() || nodeId.toLowerCase() === normSymbol.toLowerCase() || nodeId.toLowerCase().endsWith("/" + normSymbol.toLowerCase()) || nodeId.toLowerCase().endsWith("::" + normSymbol.toLowerCase())
) ?? null;
}
if (!targetNodeId) {
throw new Error(`Symbol '${symbol}' not found in the graph.`);
}
const targetAttr = graph.getNodeAttributes(targetNodeId);
const result = {
target: {
id: targetNodeId,
name: targetAttr.name,
type: targetAttr.type,
file: targetAttr.file,
line: targetAttr.startLine,
metadata: targetAttr.metadata,
links: {
incoming: graph.inDegree(targetNodeId),
outgoing: graph.outDegree(targetNodeId)
}
},
incoming: [],
outgoing: []
};
const collectEdges = (nodeId, isOutgoing, seenKeys) => {
const iterator = isOutgoing ? graph.forEachOutEdge.bind(graph) : graph.forEachInEdge.bind(graph);
iterator(nodeId, (edge, attr, source, target) => {
const neighborId = isOutgoing ? target : source;
if (neighborId === targetNodeId) return;
const key = `${neighborId}:${attr.type}`;
if (seenKeys.has(key)) return;
seenKeys.add(key);
const neighborAttr = graph.getNodeAttributes(neighborId);
const nodeInfo = {
id: neighborId,
name: neighborAttr.name,
type: neighborAttr.type,
file: neighborAttr.file,
line: neighborAttr.startLine
};
if (isOutgoing) {
result.outgoing.push({
relation: attr.type,
confidence: attr.confidence,
target: nodeInfo
});
} else {
result.incoming.push({
relation: attr.type,
confidence: attr.confidence,
source: nodeInfo
});
}
});
};
const seenIn = /* @__PURE__ */ new Set();
const seenOut = /* @__PURE__ */ new Set();
collectEdges(targetNodeId, false, seenIn);
collectEdges(targetNodeId, true, seenOut);
return result;
}
export {
queryGraph
};