@nithin554/code-crawler 0.1.62 → 0.1.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +73 -15
  2. package/dist/config.d.ts +40 -1
  3. package/dist/config.js +88 -4
  4. package/dist/config.js.map +1 -1
  5. package/dist/crawler.js +7 -8
  6. package/dist/crawler.js.map +1 -1
  7. package/dist/index.js +6 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/parser/language.d.ts +16 -0
  10. package/dist/parser/languages/index.d.ts +4 -2
  11. package/dist/parser/languages/index.js +8 -4
  12. package/dist/parser/languages/index.js.map +1 -1
  13. package/dist/parser/languages/java.d.ts +2 -2
  14. package/dist/parser/languages/java.js +3 -0
  15. package/dist/parser/languages/java.js.map +1 -1
  16. package/dist/parser/languages/python.d.ts +2 -2
  17. package/dist/parser/languages/python.js +3 -0
  18. package/dist/parser/languages/python.js.map +1 -1
  19. package/dist/parser/languages/typescript.d.ts +2 -2
  20. package/dist/parser/languages/typescript.js +3 -0
  21. package/dist/parser/languages/typescript.js.map +1 -1
  22. package/dist/resolver/genericResolver.d.ts +7 -1
  23. package/dist/resolver/genericResolver.js +13 -15
  24. package/dist/resolver/genericResolver.js.map +1 -1
  25. package/dist/resolver/javaResolver.d.ts +6 -1
  26. package/dist/resolver/javaResolver.js +9 -0
  27. package/dist/resolver/javaResolver.js.map +1 -1
  28. package/dist/resolver/registry.d.ts +15 -0
  29. package/dist/resolver/registry.js +36 -0
  30. package/dist/resolver/registry.js.map +1 -0
  31. package/dist/resolver/types.d.ts +22 -0
  32. package/dist/resolver/types.js.map +1 -1
  33. package/package.json +3 -2
  34. package/skills/code-crawler/SKILL.md +180 -0
  35. package/skills/code-crawler/reference/edge-semantics.md +110 -0
  36. package/skills/code-crawler/reference/tools.md +108 -0
@@ -0,0 +1,180 @@
1
+ ---
2
+ name: code-crawler
3
+ description: Answer questions about code relationships in an indexed repository - callers, callees, impact, entry points, routes, DI wiring, dead code, cycles - using the code-crawler MCP server's SQLite property graph. Use when the question is about how symbols connect (who calls this, what breaks if I change it, where does a request enter, is this reachable) rather than about text you can find by searching. Do not crawl a repository just to answer a question about a single file you already have open.
4
+ ---
5
+
6
+ # Code Crawler
7
+
8
+ A SQLite property graph of a codebase, exposed as 27 MCP tools. The graph is built
9
+ once by a crawl, then queried cheaply many times.
10
+
11
+ ## Getting the tools
12
+
13
+ **Check first.** If no `code-crawler` tools appear in your tool list, the server is not
14
+ registered and nothing else in this skill will run. Say so instead of falling back to
15
+ `grep` — and ask before editing a client config file, which is the user's machine
16
+ state, not yours.
17
+
18
+ Installing the *skill* does not install the *server*: `npx skills add nithin554/code-crawler`
19
+ delivers these instructions, and `npx add-mcp @nithin554/code-crawler` — or the config
20
+ below — registers the server that delivers the tools. Both are needed, and they are
21
+ independent.
22
+
23
+ The server is the npm package `@nithin554/code-crawler` and needs Node.js ≥ 22.12. Every
24
+ stdio MCP client takes the same command/args/env triple:
25
+
26
+ ```json
27
+ {
28
+ "mcpServers": {
29
+ "code-crawler": {
30
+ "command": "npx",
31
+ "args": ["-y", "@nithin554/code-crawler", "serve"],
32
+ "env": { "CODECRAWLER_DB": "/absolute/path/to/index.db" }
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ `npx -y` needs no prior install. If the package is already installed globally, use
39
+ `"command": "codecrawler"` with `"args": ["serve"]`. From a source checkout, point
40
+ `"command": "node"` at `dist/index.js`, keeping `"args": ["serve"]`. `serve` is the default
41
+ subcommand, so `args` may omit it.
42
+
43
+ Writing that JSON by hand is the slow way. `add-mcp` fills it in for whichever client you
44
+ name. Run it from inside the repository you want indexed:
45
+
46
+ ```bash
47
+ npx add-mcp @nithin554/code-crawler --args serve -a cursor
48
+ ```
49
+
50
+ It derives the server name `code-crawler` from the package, so no `--name` is needed. `-a`
51
+ accepts `claude-code`, `cursor`, `vscode`, `claude-desktop`, `codex`, `opencode`, `zed` and
52
+ about twenty more — `npx add-mcp list-agents` prints the list. Without `-g` the entry lives
53
+ in the project (`.mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json`); `-g` writes user-level
54
+ config. Repeat `--args` and `--env` once per value.
55
+
56
+ `--env CODECRAWLER_DB` is optional: the server indexes into `<project>/.code-crawler/index.db`
57
+ by default. Pin it — `--env CODECRAWLER_DB=/absolute/path/index.db` — when you want the file
58
+ elsewhere, or when the client's working directory is not the project.
59
+
60
+ Three things decide whether the graph is usable:
61
+
62
+ - **`CODECRAWLER_DB`** is the SQLite file holding the graph. The default needs no
63
+ configuration: the server walks up from its working directory to the nearest project root
64
+ (`.git`, `package.json`, `pom.xml`, `build.gradle`, `pyproject.toml`, `setup.py`,
65
+ `requirements.txt`, `go.mod`, `Cargo.toml`) and uses `<project>/.code-crawler/index.db`, so
66
+ **each repository gets its own file**. With no enclosing project it falls back to
67
+ `~/.code-crawler/index.db`. That home file is the fallback and nothing more, never an
68
+ enclosing project, so an unmarked folder under `~` gets its own file rather than sharing
69
+ it. A database still holds exactly one project — crawling a
70
+ different path into one is refused before anything is written; give that project its own
71
+ database (its own registration), or run `clear_index` to replace the existing index.
72
+ - **One server per project.** A process opens one database at startup and a database holds
73
+ one project, so register the server once per repository (project-scoped config, above)
74
+ rather than once globally for all of them. `crawl` resolves the same way, so crawling a
75
+ subdirectory such as `repo/src` shares `repo`'s file instead of growing a second one.
76
+ - **Never share one `CODECRAWLER_DB` across repositories.** Do that and the second project's
77
+ crawl hits the one-project error above.
78
+ - **Use absolute paths.** Clients launch servers from an unpredictable working directory,
79
+ so a relative `CODECRAWLER_DB` or a relative project root lands somewhere unexpected —
80
+ and the default resolution follows the cwd you may not have chosen.
81
+ - **Config is all environment variables**, identical for the CLI and the client:
82
+ `CODECRAWLER_DB`, `CODECRAWLER_EXCLUDE_DIRS`, `CODECRAWLER_DEFAULT_LIMIT`,
83
+ `CODECRAWLER_MAX_DEPTH`, `CODECRAWLER_MAX_FILE_BYTES`, `CODECRAWLER_RESPECT_GITIGNORE`,
84
+ `CODECRAWLER_HELPFUL_ERRORS`. Invalid values are clamped and warned about, never fatal.
85
+
86
+ Some clients namespace tool names with the server key — `code-crawler-search_symbols`
87
+ rather than `search_symbols`. Same tools, so match on the suffix if the exact name is
88
+ missing.
89
+
90
+ Once the tools appear, call `index_status` to confirm the server answers, then follow the
91
+ bootstrap below. The CLI shares the database, so `npx -y @nithin554/code-crawler crawl
92
+ /path/to/repo` warms the index ahead of time if you would rather not spend a turn on it.
93
+
94
+ ## When to reach for it
95
+
96
+ The graph answers **relationship** questions. Text search answers **location** questions.
97
+ Picking the wrong one wastes the most time of anything in this skill.
98
+
99
+ | Question | Use |
100
+ | --- | --- |
101
+ | Who calls this method? | `get_callers` |
102
+ | What does this method call? | `get_callees` |
103
+ | What breaks if I change this? | `impact_analysis` |
104
+ | Where does a request enter this system? | `find_entry_points`, `find_routes` |
105
+ | Which implementation handles this interface? | `find_implementations` |
106
+ | Is this method reachable at all? | `find_dead_code`, `find_hotspots` |
107
+ | How does this request reach that handler? | `trace_path`, `trace_execution` |
108
+ | What is wired in by DI or annotations? | `find_injections`, `find_annotated`, `get_decorators` |
109
+ | What third-party code is actually used? | `find_external_dependencies` |
110
+ | Where is `ThingFactory` defined? | **plain search** - do not crawl |
111
+ | Show me the body of this function | **plain file read** - do not crawl |
112
+
113
+ Crawling is not free: it parses every file in the project. Do it when you intend to ask
114
+ several relationship questions, or when one relationship question has no cheaper answer.
115
+ For a single-file question you already have open, reading the file beats crawling the repo.
116
+
117
+ ## Bootstrap
118
+
119
+ 1. `index_status` first. If it reports files and a recent crawl, the index is already
120
+ warm and you can skip step 2.
121
+ 2. `crawl_project` with the project root. It is idempotent and incremental - unchanged
122
+ files are skipped via a content manifest, so a second crawl is cheap.
123
+ 3. Only then run the query tools.
124
+
125
+ **Never trust a stale index.** Call `index_status` before concluding "there are no
126
+ callers". An empty result from an empty or stale index is indistinguishable from a true
127
+ negative, and that is the single most common way this tool misleads.
128
+
129
+ Re-index with the `force` option when the working tree changed underneath the index:
130
+
131
+ - after a rebase, merge, or `git reset` that rewrote history
132
+ - after switching branches with a large diff
133
+ - after `git stash pop`
134
+
135
+ An incremental crawl keys off file content and mtime, which is enough for ordinary
136
+ editing but not for a history rewrite. `force: true` bypasses the manifest.
137
+
138
+ ## Reading the results
139
+
140
+ Two fields decide whether a result means what you think: `confidence` and `external`.
141
+ Phantom nodes mark the third trap. See
142
+ [reference/edge-semantics.md](reference/edge-semantics.md) before reasoning about
143
+ "unreachable" or "no callers".
144
+
145
+ Short version:
146
+
147
+ - `confidence` is not a probability. It is a band: `1.0` resolved by construction,
148
+ `0.9`/`0.85` strong static evidence, `0.8` inherited or external, `0.5` heuristics,
149
+ `0` phantom.
150
+ - `external: true` means the target is outside the project. It is *not* dead code and
151
+ *not* unresolved.
152
+ - `__phantom__:` / `__reflect__:` / `__inject__:` targets mark something the resolver
153
+ could not bind. They are inventory, not findings.
154
+
155
+ ## Reference
156
+
157
+ - [reference/tools.md](reference/tools.md) - all 27 tools, grouped by the question they
158
+ answer, with the argument that matters most for each.
159
+ - [reference/edge-semantics.md](reference/edge-semantics.md) - `confidence`, `external`,
160
+ phantom nodes, and the ways a "missing" edge is legitimate.
161
+
162
+ ## Budget discipline
163
+
164
+ Every list-returning tool accepts `max_tokens` (100-200000) and `cursor`. The response
165
+ then carries a `page` block with `totalCount`, `truncated`, and `nextCursor`.
166
+
167
+ Start with a small `max_tokens` and widen only if the answer is genuinely incomplete.
168
+ Reading one page of `get_callers` and reporting the shape is almost always better than
169
+ paging through all of them. Prefer `explain_symbol` over chaining `get_node` +
170
+ `get_callers` + `get_callees` + `get_decorators` when you want a digest rather than a
171
+ specific list.
172
+
173
+ ## Do not
174
+
175
+ - Crawl a monorepo to answer a question about one file.
176
+ - Report "no callers" or "dead code" without checking `confidence` and `external` on the
177
+ edges you did get.
178
+ - Treat `find_unresolved` as a bug list. It is a coverage report; some entries are
179
+ dynamic dispatch that cannot be resolved statically.
180
+ - Page through an entire result set to count things. Use `totalCount` from the first page.
@@ -0,0 +1,110 @@
1
+ # Edge semantics
2
+
3
+ Three things cause most misreads of this graph: `confidence`, `external`, and phantom
4
+ nodes. Read this before reporting "no callers", "dead code", or "unresolved".
5
+
6
+ ## Edge kinds
7
+
8
+ | Kind | Means |
9
+ | --- | --- |
10
+ | `CALLS` | A statically resolved call |
11
+ | `DYNAMIC_DISPATCH` | A call that reaches an implementation through an interface or superclass |
12
+ | `ADVISES` | Around/before/after advice intercepting a method |
13
+ | `DECLARED_IN` | Symbol to its containing type |
14
+ | `CONTAINS` | Type to its members, module to its declarations |
15
+ | `IMPLEMENTS` | Type to the interface it implements |
16
+ | `EXTENDS` | Type to its superclass |
17
+ | `IMPORTS` | File to the module it imports |
18
+ | `ANNOTATED_WITH` | Symbol to an annotation or decorator |
19
+ | `REFERENCES` | A non-call reference, such as a field or type use |
20
+ | `REFLECTS` | A runtime lookup that may reach a symbol |
21
+ | `INJECTS` | A DI-supplied dependency |
22
+ | `ROUTES` | An HTTP route to its handler |
23
+
24
+ A method reached only by `DYNAMIC_DISPATCH`, `ADVISES`, or `ROUTES` **is still called**.
25
+ When asking "who calls this", look at all inbound edge kinds, not just `CALLS`.
26
+
27
+ ## `confidence` is a band, not a probability
28
+
29
+ Do not average it, threshold it, or present it as certainty. It is a coarse label telling
30
+ you *what kind of evidence* produced the edge.
31
+
32
+ | Value | Evidence |
33
+ | --- | --- |
34
+ | `1.0` | Resolved by construction - a constructor or overload matched exactly |
35
+ | `0.9` | Same-module declaration, typed local, or default constructor |
36
+ | `0.85` | Imported from a named module |
37
+ | `0.8` | Inherited from a supertype, or an external target |
38
+ | `0.75` | Call to an imported class resolved through its import |
39
+ | `0.6` | Pointcut `args(...)` matched on arity only |
40
+ | `0.55` | Pointcut primitive matched by static approximation |
41
+ | `0.3` | Empty pointcut - the advice matches everything |
42
+ | `0` | Phantom - nothing was resolved |
43
+
44
+ Practical rule: `>= 0.75` is safe to report as fact, `0.5`-`0.7` deserves a hedge, and `0`
45
+ carries no information about the target at all.
46
+
47
+ ## `external` means outside the project
48
+
49
+ An edge with `external: true` points at a node like `ext:axios` or `ext:fastapi`, and its
50
+ `externalModule` names the package.
51
+
52
+ Consequences that are easy to get wrong:
53
+
54
+ - An external target is **not** dead code. It is a real dependency you do not index.
55
+ - An external target is **not** an unresolved call. It was resolved, just to the boundary.
56
+ - `find_dead_code` ignores external targets, so a method whose only caller is external can
57
+ still look dead. Cross-check `find_external_dependencies`.
58
+
59
+ ## Phantom nodes
60
+
61
+ A phantom target means the resolver saw a call but could not name its destination. Phantoms
62
+ carry `confidence: 0` and `resolved: false`, and their ids are prefixed:
63
+
64
+ | Prefix | Origin |
65
+ | --- | --- |
66
+ | `__phantom__:` | A call site with no resolvable target |
67
+ | `__reflect__:` | A runtime lookup whose type or method was not found |
68
+ | `__inject__:` | An injection marker that named neither a type nor a package |
69
+
70
+ Phantoms are **inventory, not findings**. A phantom is a statement about the analyser, not
71
+ about the code. Never report one as "calls something unknown" without reading its `reason` -
72
+ that field says which lookup step failed.
73
+
74
+ ## Why "no callers" can be legitimate and wrong at the same time
75
+
76
+ Five ways a real caller produces no inbound `CALLS` edge:
77
+
78
+ 1. **The index is stale.** Always `index_status` first. This is the most common cause.
79
+ 2. **Dynamic dispatch.** The call goes through an interface; look for
80
+ `DYNAMIC_DISPATCH` inbound edges and use `find_implementations`.
81
+ 3. **Framework invocation.** Routes, listeners, schedulers, and tests are entry points,
82
+ not calls. Check `find_entry_points` and `find_routes`.
83
+ 4. **Reflection.** `Class.forName`, `getattr`, `importlib`, and dynamic `import()` produce
84
+ `REFLECTS` edges, not `CALLS`. Check `find_reflections`.
85
+ 5. **The caller is external.** Only in-project call edges are recorded. Check
86
+ `find_external_dependencies`.
87
+
88
+ So the correct phrasing is "no in-project static callers", not "unused".
89
+
90
+ ## Stale indexes and `force`
91
+
92
+ Incremental crawls compare file content and mtime against a manifest. That covers normal
93
+ editing. It does **not** cover the working tree changing underneath the index:
94
+
95
+ - after a rebase, merge, or `git reset` that rewrote history
96
+ - after switching branches with a large diff
97
+ - after `git stash pop`
98
+
99
+ A file whose content changed but whose mtime was reset can be skipped, leaving edges that
100
+ point at declarations that no longer exist. Pass `force: true` to `crawl_project` in those
101
+ cases. If results look impossible, forcing a re-index is cheaper than debugging the graph.
102
+
103
+ ## Token budgeting
104
+
105
+ List-returning tools accept `max_tokens` and `cursor`, adding a `page` block with
106
+ `totalCount`, `returnedCount`, `truncated`, `estimatedTokens`, and `nextCursor`.
107
+
108
+ Truncation is reported, never silent: if `truncated` is true the answer is incomplete and
109
+ you should either widen `max_tokens` or say so. Paging never returns an empty page while
110
+ items remain, so a continuation loop terminates.
@@ -0,0 +1,108 @@
1
+ # Tool reference
2
+
3
+ All 27 tools, grouped by the question they answer. Run `index_status` before trusting any
4
+ result, and `crawl_project` before that if the index is empty.
5
+
6
+ ## Orientation
7
+
8
+ Call these to establish what the index contains, not to answer questions about code.
9
+
10
+ | Tool | Use it when | Key argument |
11
+ | --- | --- | --- |
12
+ | `crawl_project` | The index is empty, or files changed on disk | `path` (root), `force` |
13
+ | `index_status` | Before every session, and before believing a negative result | none |
14
+ | `get_schema` | You need the node/edge vocabulary or property names | none |
15
+ | `clear_index` | A crawl was rooted at the wrong directory and the graph is unusable | none |
16
+
17
+ `crawl_project` is incremental by default. Pass `force: true` only after a history rewrite,
18
+ branch switch, or stash pop - see [edge-semantics.md](edge-semantics.md) for why.
19
+
20
+ ## Find a symbol
21
+
22
+ | Tool | Use it when | Key argument |
23
+ | --- | --- | --- |
24
+ | `search_symbols` | You have a name fragment but not a node id | `query`, `kind` |
25
+ | `get_node` | You have a node id and want its metadata and source location | `id` |
26
+ | `explain_symbol` | You want one digest instead of four separate calls | `id` or `name` |
27
+
28
+ `explain_symbol` returns members, decorators, callers, callees, and framework wiring in one
29
+ payload. Reach for it first when exploring an unfamiliar symbol; drop to `get_callers` or
30
+ `get_callees` when you need the full list rather than a summary.
31
+
32
+ ## Relationships
33
+
34
+ | Tool | Use it when | Key argument |
35
+ | --- | --- | --- |
36
+ | `get_callers` | "Who calls this?" - direct or transitive | `id`, `depth` |
37
+ | `get_callees` | "What does this call?" - direct or transitive | `id`, `depth` |
38
+ | `trace_path` | "How does execution get from A to B?" | `from`, `to` |
39
+ | `trace_execution` | You need dynamic dispatch and woven advice, not just static calls | `id` |
40
+ | `get_subgraph` | You want the neighbourhood as data for a diagram | `id`, `radius`, `limit` |
41
+ | `export_graph` | You want Mermaid or Graphviz text you can paste | `id`, `format` |
42
+
43
+ `get_callers`/`get_callees` take `depth` - keep it at 1 unless the question is explicitly
44
+ transitive, because depth 3 on a hub method returns most of the repository.
45
+
46
+ `get_subgraph` is bounded by `radius`/`limit` rather than `max_tokens`, because its `nodes`
47
+ and `edges` are two views of one slice and cutting one list leaves dangling references.
48
+
49
+ ## Impact and risk
50
+
51
+ | Tool | Use it when | Key argument |
52
+ | --- | --- | --- |
53
+ | `impact_analysis` | "What breaks if I change this?" - the one call to prefer | `id` |
54
+ | `find_implementations` | You have an interface and need its implementers | `id` |
55
+ | `find_cycles` | You suspect mutual recursion or a dependency knot | none |
56
+ | `find_dead_code` | You want methods with no incoming edges at all | none |
57
+ | `find_hotspots` | You want the highest fan-in/fan-out methods ranked | `limit` |
58
+
59
+ `impact_analysis` bundles callers, entry points, routes, overrides, and a risk band. Prefer
60
+ it over assembling the same answer from four calls.
61
+
62
+ `find_dead_code` reports *no incoming call, dispatch, or advice edge*. Treat that as a
63
+ candidate list: a method invoked purely by reflection or by an external framework will
64
+ appear here. Cross-check with `find_reflections` and `find_entry_points` before deleting
65
+ anything.
66
+
67
+ ## Framework wiring
68
+
69
+ These tools exist because the call graph alone cannot see how a framework invokes code.
70
+
71
+ | Tool | Use it when | Key argument |
72
+ | --- | --- | --- |
73
+ | `find_entry_points` | "Where does this system begin?" | `kind` |
74
+ | `find_routes` | You want HTTP route to handler mappings | `framework` |
75
+ | `find_injections` | You want DI-supplied dependencies | `framework` |
76
+ | `find_annotated` | You want every symbol carrying one annotation | `annotation` |
77
+ | `get_decorators` | You want the decorators on one symbol | `id`, `includeMembers` |
78
+ | `find_aspects` | You want aspects, advice kinds, and intercepted methods | none |
79
+ | `find_reflections` | You want runtime lookups (`Class.forName`, `getattr`, `import()`) | none |
80
+
81
+ Entry-point kinds include HTTP routes, listeners, schedulers, CLI mains, and tests. If a
82
+ method looks dead but is plausibly framework-invoked, check `find_entry_points` and
83
+ `find_reflections` before concluding it is unreachable.
84
+
85
+ ## Coverage and dependencies
86
+
87
+ | Tool | Use it when | Key argument |
88
+ | --- | --- | --- |
89
+ | `find_unresolved` | You need to know how much of the graph failed to bind | none |
90
+ | `find_external_dependencies` | You want the third-party packages actually referenced | none |
91
+
92
+ `find_unresolved` is grouped by failure reason, which is the useful part: a cluster of one
93
+ reason usually means one modelling gap, not many broken call sites.
94
+
95
+ `find_external_dependencies` lists packages with reference and file counts. Use it to answer
96
+ "do we actually use this dependency?" - an empty result for a package in `package.json` means
97
+ nothing in the indexed code references it.
98
+
99
+ ## Budgeting
100
+
101
+ Every list-returning tool accepts `max_tokens` and `cursor`; passing either adds a `page`
102
+ block with `totalCount`, `returnedCount`, `truncated`, `estimatedTokens`, and `nextCursor`.
103
+ Omit both and the response shape is unchanged.
104
+
105
+ Aggregate tools (`impact_analysis`, `explain_symbol`, `export_graph`) and single-object
106
+ tools (`get_node`, `index_status`, `get_schema`) return one object and take no budget.
107
+
108
+ Use `totalCount` from the first page instead of paging to count.