Skip to content

Commit b5e47c1

Browse files
committed
feat: show deps and available workspace members in crate overview
When listing a crate's items with `rspeek <crate>`, the output now includes direct dependencies and workspace members that aren't yet deps. Helps LLMs discover what's already available before writing code from scratch. Crate name comparison normalizes hyphens/underscores to avoid false positives in the available list.
1 parent 8bf777f commit b5e47c1

File tree

4 files changed

+76
-13
lines changed

4 files changed

+76
-13
lines changed

README.md

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
Inspect type definitions from Rust crate source code without building docs.
44

5-
Searches dependency crates and workspace member crates — including integration
6-
tests in `tests/`.
5+
Searches dependency crates and workspace member crates for structs, enums,
6+
unions, traits, type aliases, constants, statics, and functions — including
7+
integration tests in `tests/`.
78

89
## Installation
910

@@ -94,23 +95,31 @@ Found 7 matches for `Error`:
9495
- struct `serde_json::error::Error` at /home/user/.cargo/.../error.rs:15
9596
```
9697

97-
Crate overview — source path and item listing:
98+
Crate overview — source path, dependencies, and item listing:
9899

99100
```
100-
## `anyhow` v1.0.102
101-
**Source:** `/home/user/.cargo/registry/src/.../anyhow-1.0.102/src`
101+
## `serde_json` v1.0.149
102+
**Source:** `/home/user/.cargo/registry/src/.../serde_json-1.0.149/src`
103+
104+
**Dependencies:** itoa, memchr, serde, serde_core, zmij
105+
106+
**Workspace members (not yet deps):** my-utils, my-core
102107
103108
Public items:
104109
105-
- struct `Error`
106-
- struct `Chain`
107-
- type `Result`
108-
- trait `Context`
110+
- struct `de::Deserializer`
111+
- fn `de::from_reader`
112+
- fn `de::from_slice`
113+
- fn `de::from_str`
114+
- struct `error::Error`
115+
- enum `value::Value`
109116
110-
Usage: `rspeek anyhow <Item>`
117+
Usage: `rspeek serde_json <Item>`
111118
```
112119

113120
For workspace members, all items are shown (not just public).
121+
**Dependencies** and **Workspace members (not yet deps)** help you discover
122+
what's already usable and what can be added with a one-line Cargo.toml edit.
114123

115124
### JSON output
116125

@@ -160,6 +169,6 @@ Use `--llm-help` for extended usage documentation suitable for tool descriptions
160169

161170
## Limitations
162171

163-
- Only finds items defined as regular Rust syntax (`struct`, `enum`, `trait`, `type`, `fn`)
172+
- Only finds items defined as regular Rust syntax (`struct`, `enum`, `union`, `trait`, `type`, `fn`, `const`, `static`)
164173
- Macro bodies are parsed for item definitions (e.g., syn's `ast_struct!`), but procedural macros and complex `macro_rules!` patterns are not expanded
165174
- Re-exports: `pub use` within a crate are followed; glob re-exports and cross-crate re-exports are not

src/main.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ const LLM_HELP: &str = r#"# rspeek — inspect type definitions from Rust depend
3838
3939
## Usage
4040
41-
rspeek is designed to be called by LLMs to look up structs, enums, traits,
42-
type aliases, and functions from Rust crates without building docs.
41+
rspeek is designed to be called by LLMs to look up structs, enums, unions,
42+
traits, type aliases, constants, statics, and functions from Rust crates
43+
without building docs.
4344
4445
Run from any directory containing a Cargo.toml. Searches dependency crates
4546
and workspace member crates (including integration tests in `tests/`).
@@ -78,12 +79,26 @@ crate version, and the original source in a fenced code block.
7879
Multiple matches: summary list with kind, qualified path, and location.
7980
Narrow the search with a crate name or qualified path.
8081
82+
## Crate overview
83+
84+
`rspeek <crate>` lists public items and also shows:
85+
- **Dependencies**: direct deps of the crate (already in Cargo.toml)
86+
- **Workspace members (not yet deps)**: sibling crates that can be added
87+
with a one-line Cargo.toml edit
88+
89+
Use this to check what's already available before writing code from scratch.
90+
8191
## JSON output
8292
8393
--json returns an array of objects:
8494
[{"name", "kind", "module_path", "file", "start_line", "end_line",
8595
"crate_name", "crate_version", "source", "impls"?}]
8696
97+
Crate overview (rspeek --json <crate>) returns:
98+
[{"crate_name", "crate_version", "src_dir", "items": [...],
99+
"deps": ["anyhow", ...],
100+
"available_workspace_members": ["my-utils", ...]}]
101+
87102
Errors are also JSON: {"error": "...", "suggestions": ["..."]}
88103
89104
## Did you mean?
@@ -203,6 +218,8 @@ fn run(cli: &Cli) -> std::result::Result<(), NotFound> {
203218
module_path: i.module_path,
204219
})
205220
.collect(),
221+
deps: c.deps.clone(),
222+
available_workspace_members: ws.available_members(&c.name, &c.deps),
206223
});
207224
}
208225
println!("{}", serde_json::to_string(&overviews).unwrap());
@@ -217,6 +234,16 @@ fn run(cli: &Cli) -> std::result::Result<(), NotFound> {
217234
.collect::<Vec<_>>()
218235
.join(", ")
219236
);
237+
if !c.deps.is_empty() {
238+
println!("**Dependencies:** {}\n", c.deps.join(", "));
239+
}
240+
let available = ws.available_members(&c.name, &c.deps);
241+
if !available.is_empty() {
242+
println!(
243+
"**Workspace members (not yet deps):** {}\n",
244+
available.join(", ")
245+
);
246+
}
220247
let pub_only = !c.is_workspace_member;
221248
let items = index::list_items(&c.source_dirs, pub_only, c.out_dir.as_deref())
222249
.map_err(|e| NotFound {

src/output.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ pub struct JsonCrateOverview {
3535
pub crate_version: String,
3636
pub src_dir: String,
3737
pub items: Vec<JsonItem>,
38+
#[serde(skip_serializing_if = "Vec::is_empty")]
39+
pub deps: Vec<String>,
40+
#[serde(skip_serializing_if = "Vec::is_empty")]
41+
pub available_workspace_members: Vec<String>,
3842
}
3943

4044
#[derive(Serialize)]

src/resolve.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub struct ResolvedCrate {
1313
pub is_workspace_member: bool,
1414
/// Build script OUT_DIR, if the crate has been built.
1515
pub out_dir: Option<PathBuf>,
16+
/// Direct dependency crate names.
17+
pub deps: Vec<String>,
1618
}
1719

1820
/// Normalize crate name for comparison (treat `-` and `_` as equivalent).
@@ -97,12 +99,14 @@ impl Workspace {
9799
}
98100
}
99101
let out_dir = find_out_dir(metadata.target_directory.as_std_path(), &pkg.name);
102+
let deps: Vec<String> = node.deps.iter().map(|d| d.name.clone()).collect();
100103
crates.push(ResolvedCrate {
101104
name: pkg.name.to_string(),
102105
version: pkg.version.to_string(),
103106
source_dirs,
104107
is_workspace_member: is_member,
105108
out_dir,
109+
deps,
106110
});
107111
}
108112
}
@@ -128,4 +132,23 @@ impl Workspace {
128132
.filter(|c| normalize(&c.name) == norm)
129133
.collect()
130134
}
135+
136+
/// Names of all workspace member crates.
137+
pub fn member_names(&self) -> Vec<&str> {
138+
self.crates
139+
.iter()
140+
.filter(|c| c.is_workspace_member)
141+
.map(|c| c.name.as_str())
142+
.collect()
143+
}
144+
145+
/// Workspace members that are not already dependencies of `crate_name`.
146+
pub fn available_members(&self, crate_name: &str, deps: &[String]) -> Vec<String> {
147+
let dep_set: HashSet<String> = deps.iter().map(|s| normalize(s)).collect();
148+
self.member_names()
149+
.into_iter()
150+
.filter(|&m| m != crate_name && !dep_set.contains(&normalize(m)))
151+
.map(|m| m.to_string())
152+
.collect()
153+
}
131154
}

0 commit comments

Comments
 (0)