Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions claude-code/skills/hivemind-graph/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
name: hivemind-graph
description: Query the local code graph (functions, classes, calls, imports) through the Deeplake mount at memory/graph/. Use when the user asks structural questions about the codebase — "what calls X?", "what does Y import?", "where is Z defined?", "what's the architecture / which subsystems exist?", "what's the impact of changing this?". The graph is an AST-derived map of the repo, queried as files (no build needed — it rebuilds automatically).
allowed-tools: Read Bash
---

# Hivemind Code Graph

A deterministic, AST-derived map of the current repository — every function,
class, method, interface, type, enum, const, and module, plus the edges between
them (`calls`, `imports`, `extends`, `implements`, `method_of`). It is queried as
synthesized files under the Deeplake mount; there are no real files on disk and
no network call in the read path.

The graph **builds and refreshes automatically** (on Stop / SessionEnd, gated by
a rate limit + git diff). You never run a build command — just read it.

## When to use this skill

Activate when the user asks a *structural / relational* question about the code:

- "What calls `pushSnapshot`?" / "Who uses this function?"
- "What does `deeplake-pull.ts` import?" / "What depends on X?"
- "Where is `GraphSnapshot` defined?" / "Find the function that handles Y."
- "What are the main subsystems / the architecture here?"
- "If I change this signature, what's affected?" (1-hop blast radius)

## When NOT to use this skill

- Reading the **body** of a symbol you already located → use `Read` on the real
source file. The graph gives location + relationships, not full source.
- Code that isn't **committed/built** yet — the graph can lag uncommitted edits.
If a file's mtime is newer than the build timestamp, read the live source.
- Non-TypeScript code (Python, Go, …) — the graph is **TypeScript/TSX only**
today. For other languages, fall back to grep/read.

## Path cheat sheet

```bash
cat ~/.deeplake/memory/graph/index.md
# Overview: node/edge counts, kind breakdown, top files by node count.

cat ~/.deeplake/memory/graph/find/<pattern>
# Case-insensitive substring search on node id + label (max 50 hits).
# Prints numbered handles [1] [2] ... saved for this worktree.

cat ~/.deeplake/memory/graph/show/<handle-or-pattern>
# <handle>: a digit from a prior find/ (e.g. 3).
# <pattern>: a substring → unique node detail, or a candidate list.
# Output: the node + its 1-hop neighbors grouped by edge relation.
```

## Workflow

1. Broad? Start at `index.md` to see subsystems and the biggest files.
2. Looking for a symbol? `find/<name>` → pick the handle you want.
3. Want relationships? `show/<handle>` → see callers/callees, imports, members.
4. Need the actual code? Take the `source_file:line` from `show/` and `Read` it.

## Anti-patterns (read these)

- **"Incoming (0)" does NOT mean dead code.** Today `calls` edges are resolved
*intra-file only* — a node with zero incoming edges may still be called from
other files. Treat it as "no caller in the same file", not "unused".
- **The graph can be stale.** It rebuilds at most once per rate-limit window. The
SessionStart inject prints the build age; if it's old or you've just edited a
file, prefer the live source for that file.
- **Don't try to build it.** There is no user-facing build step in normal use;
the hooks handle it. Just read the mount.
- **`find/` is lexical, not semantic.** It matches substrings, not meaning —
`find/auth` won't surface `login`/`credentials` unless those strings appear in
the id/label. Try multiple keywords if the first misses.
72 changes: 72 additions & 0 deletions codex/skills/hivemind-graph/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
name: hivemind-graph
description: Query the local code graph (functions, classes, calls, imports) through the Deeplake mount at memory/graph/. Use when the user asks structural questions about the codebase — "what calls X?", "what does Y import?", "where is Z defined?", "what is the architecture / which subsystems exist?". The graph is an AST-derived map of the repo, queried as files (no build needed — it rebuilds automatically).
allowed-tools: Bash
---

# Hivemind Code Graph

A deterministic, AST-derived map of the current repository — every function,
class, method, interface, type, enum, const, and module, plus the edges between
them (`calls`, `imports`, `extends`, `implements`, `method_of`). It is queried as
synthesized files under the Deeplake mount; there are no real files on disk and
no network call in the read path.

The graph **builds and refreshes automatically** (on Stop / SessionEnd, gated by
a rate limit + git diff). You never run a build command — just read it.

## When to use this skill

Activate when the user asks a *structural / relational* question about the code:

- "What calls `pushSnapshot`?" / "Who uses this function?"
- "What does `deeplake-pull.ts` import?" / "What depends on X?"
- "Where is `GraphSnapshot` defined?" / "Find the function that handles Y."
- "What are the main subsystems / the architecture here?"
- "If I change this signature, what's affected?" (1-hop blast radius)

## When NOT to use this skill

- Reading the **body** of a symbol you already located → use `Read` on the real
source file. The graph gives location + relationships, not full source.
- Code that isn't **committed/built** yet — the graph can lag uncommitted edits.
If a file's mtime is newer than the build timestamp, read the live source.
- Non-TypeScript code (Python, Go, …) — the graph is **TypeScript/TSX only**
today. For other languages, fall back to grep/read.

## Path cheat sheet

```bash
cat ~/.deeplake/memory/graph/index.md
# Overview: node/edge counts, kind breakdown, top files by node count.

cat ~/.deeplake/memory/graph/find/<pattern>
# Case-insensitive substring search on node id + label (max 50 hits).
# Prints numbered handles [1] [2] ... saved for this worktree.

cat ~/.deeplake/memory/graph/show/<handle-or-pattern>
# <handle>: a digit from a prior find/ (e.g. 3).
# <pattern>: a substring → unique node detail, or a candidate list.
# Output: the node + its 1-hop neighbors grouped by edge relation.
```

## Workflow

1. Broad? Start at `index.md` to see subsystems and the biggest files.
2. Looking for a symbol? `find/<name>` → pick the handle you want.
3. Want relationships? `show/<handle>` → see callers/callees, imports, members.
4. Need the actual code? Take the `source_file:line` from `show/` and `Read` it.

## Anti-patterns (read these)

- **"Incoming (0)" does NOT mean dead code.** Today `calls` edges are resolved
*intra-file only* — a node with zero incoming edges may still be called from
other files. Treat it as "no caller in the same file", not "unused".
- **The graph can be stale.** It rebuilds at most once per rate-limit window. The
SessionStart inject prints the build age; if it's old or you've just edited a
file, prefer the live source for that file.
- **Don't try to build it.** There is no user-facing build step in normal use;
the hooks handle it. Just read the mount.
- **`find/` is lexical, not semantic.** It matches substrings, not meaning —
`find/auth` won't surface `login`/`credentials` unless those strings appear in
the id/label. Try multiple keywords if the first misses.
10 changes: 10 additions & 0 deletions esbuild.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ const cursorHooks = [
{ entry: "dist/src/hooks/cursor/wiki-worker.js", out: "wiki-worker" },
{ entry: "dist/src/skillify/skillify-worker.js", out: "skillify-worker" },
{ entry: "dist/src/hooks/graph-pull-worker.js", out: "graph-pull-worker" },
// A1 (graph Cursor parity): same auto-build hook as Claude Code, wired
// to Cursor's stop + sessionEnd events in install-cursor.ts. Reuses the
// shared src/hooks/graph-on-stop.ts entry (no per-agent logic).
{ entry: "dist/src/hooks/graph-on-stop.js", out: "graph-on-stop" },
];

// Hermes Agent shell-hook bundles (matches Claude Code's wire protocol; see
Expand Down Expand Up @@ -176,6 +180,12 @@ await build({
"onnxruntime-node",
"onnxruntime-common",
"sharp",
// graph-on-stop transitively imports the tree-sitter native parser
// (via runBuildCommand → extractTypeScript); same externalization as
// the Claude Code bundle. Native .node prebuilds resolve from
// node_modules at runtime.
"tree-sitter",
"tree-sitter-typescript",
],
define: {
__HIVEMIND_VERSION__: JSON.stringify(hivemindVersion),
Expand Down
72 changes: 72 additions & 0 deletions hermes/skills/hivemind-graph/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
name: hivemind-graph
description: Query the local code graph (functions, classes, calls, imports) through the Deeplake mount at memory/graph/. Use when the user asks structural questions about the codebase — "what calls X?", "what does Y import?", "where is Z defined?", "what is the architecture / which subsystems exist?". The graph is an AST-derived map of the repo, queried as files (no build needed — it rebuilds automatically).
allowed-tools: terminal
---

# Hivemind Code Graph

A deterministic, AST-derived map of the current repository — every function,
class, method, interface, type, enum, const, and module, plus the edges between
them (`calls`, `imports`, `extends`, `implements`, `method_of`). It is queried as
synthesized files under the Deeplake mount; there are no real files on disk and
no network call in the read path.

The graph **builds and refreshes automatically** (on Stop / SessionEnd, gated by
a rate limit + git diff). You never run a build command — just read it.

## When to use this skill

Activate when the user asks a *structural / relational* question about the code:

- "What calls `pushSnapshot`?" / "Who uses this function?"
- "What does `deeplake-pull.ts` import?" / "What depends on X?"
- "Where is `GraphSnapshot` defined?" / "Find the function that handles Y."
- "What are the main subsystems / the architecture here?"
- "If I change this signature, what's affected?" (1-hop blast radius)

## When NOT to use this skill

- Reading the **body** of a symbol you already located → use `Read` on the real
source file. The graph gives location + relationships, not full source.
- Code that isn't **committed/built** yet — the graph can lag uncommitted edits.
If a file's mtime is newer than the build timestamp, read the live source.
- Non-TypeScript code (Python, Go, …) — the graph is **TypeScript/TSX only**
today. For other languages, fall back to grep/read.

## Path cheat sheet

```bash
cat ~/.deeplake/memory/graph/index.md
# Overview: node/edge counts, kind breakdown, top files by node count.

cat ~/.deeplake/memory/graph/find/<pattern>
# Case-insensitive substring search on node id + label (max 50 hits).
# Prints numbered handles [1] [2] ... saved for this worktree.

cat ~/.deeplake/memory/graph/show/<handle-or-pattern>
# <handle>: a digit from a prior find/ (e.g. 3).
# <pattern>: a substring → unique node detail, or a candidate list.
# Output: the node + its 1-hop neighbors grouped by edge relation.
```

## Workflow

1. Broad? Start at `index.md` to see subsystems and the biggest files.
2. Looking for a symbol? `find/<name>` → pick the handle you want.
3. Want relationships? `show/<handle>` → see callers/callees, imports, members.
4. Need the actual code? Take the `source_file:line` from `show/` and `Read` it.

## Anti-patterns (read these)

- **"Incoming (0)" does NOT mean dead code.** Today `calls` edges are resolved
*intra-file only* — a node with zero incoming edges may still be called from
other files. Treat it as "no caller in the same file", not "unused".
- **The graph can be stale.** It rebuilds at most once per rate-limit window. The
SessionStart inject prints the build age; if it's old or you've just edited a
file, prefer the live source for that file.
- **Don't try to build it.** There is no user-facing build step in normal use;
the hooks handle it. Just read the mount.
- **`find/` is lexical, not semantic.** It matches substrings, not meaning —
`find/auth` won't surface `login`/`credentials` unless those strings appear in
the id/label. Try multiple keywords if the first misses.
8 changes: 6 additions & 2 deletions src/cli/install-cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,12 @@ function buildHookConfig(): Record<string, CursorHookEntry[]> {
preToolUse: [buildHookCmdShellMatcher("pre-tool-use.js", 30)],
postToolUse: [buildHookCmd("capture.js", 15)],
afterAgentResponse: [buildHookCmd("capture.js", 15)],
stop: [buildHookCmd("capture.js", 15)],
sessionEnd: [buildHookCmd("session-end.js", 30)],
// graph-on-stop: auto-build the code graph (A1 Cursor parity). Same hook
// Claude Code registers under Stop + SessionEnd. It's gated (rate limit +
// HEAD-changed + source-diff) so the common path is a ~5ms skip, and runs
// async so it never blocks Cursor.
stop: [buildHookCmd("capture.js", 15), buildHookCmd("graph-on-stop.js", 30)],
sessionEnd: [buildHookCmd("session-end.js", 30), buildHookCmd("graph-on-stop.js", 30)],
};
}

Expand Down
28 changes: 23 additions & 5 deletions src/graph/deeplake-pull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ import { sqlIdent, sqlStr } from "../utils/sql.js";
import { deriveProjectKey } from "../utils/repo-identity.js";
import { writeLastBuild, readLastBuild } from "./last-build.js";
import { appendHistoryEntry } from "./history.js";
import { repoDir } from "./snapshot.js";
import { computeSnapshotSha256, repoDir } from "./snapshot.js";
import type { GraphSnapshot } from "./types.js";

export type PullOutcome =
| { kind: "skipped-no-auth" }
Expand Down Expand Up @@ -148,11 +149,28 @@ export async function pullSnapshot(
if (cloudPayload === null) {
return errorOutcome("SELECT cloud row", new Error("invalid snapshot_jsonb payload"));
}
// Hash mismatch = the API returned the wrong bytes for the claimed
// sha256. Refuse rather than poison the local cache. Empty sha is
// permitted (legacy rows that predate the column being populated).
// Parse the payload so we can recompute the *stable-field* hash the same
// way push does (see computeSnapshotSha256 in src/graph/snapshot.ts).
// The payload bytes intentionally include `observation` (build-time
// metadata), so hashing them directly would never match the column —
// which by contract excludes observation so identical code on different
// worktrees/branches/timestamps dedups.
let parsedSnapshot: GraphSnapshot;
try {
parsedSnapshot = JSON.parse(cloudPayload) as GraphSnapshot;
} catch (err) {
return errorOutcome("parse cloud snapshot", err);
}
if (!Array.isArray((parsedSnapshot as { nodes?: unknown }).nodes) ||
!Array.isArray((parsedSnapshot as { links?: unknown }).links)) {
return errorOutcome("parse cloud snapshot", new Error("snapshot missing nodes/links arrays"));
Comment on lines +158 to +166
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject JSON null and other non-object payloads before touching nodes/links.

JSON.parse("null") succeeds here, and the subsequent property access throws before pullSnapshot can return the documented error outcome. Please first guard that the parsed value is a non-null object, then validate the snapshot shape before hashing or writing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/graph/deeplake-pull.ts` around lines 158 - 166, After JSON.parse you must
guard against non-object results (e.g., JSON null) before accessing properties;
update the code around parsedSnapshot (the variable produced in the try/catch)
to first check that typeof parsedSnapshot === "object" && parsedSnapshot !==
null and return errorOutcome("parse cloud snapshot", new Error("snapshot not an
object")) if that fails, then perform the existing Array.isArray checks on
(parsedSnapshot as { nodes?: unknown }).nodes and .links; ensure no property
access occurs until the non-null object guard passes so pullSnapshot returns the
documented error outcome for invalid payloads.

}
// Hash mismatch = the API returned a snapshot whose stable fields don't
// match the claimed sha256. Refuse rather than poison the local cache.
// Empty sha is permitted (legacy rows that predate the column being
// populated).
if (cloudSha256 !== "") {
const computedSha = createHash("sha256").update(cloudPayload).digest("hex");
const computedSha = computeSnapshotSha256(parsedSnapshot);
if (cloudSha256 !== computedSha) {
return errorOutcome("SELECT cloud row", new Error(`snapshot_sha256 mismatch (expected ${cloudSha256}, got ${computedSha})`));
}
Expand Down
Loading
Loading