Skip to content

add doc retrieval#12

Merged
Bechma merged 1 commit intocyberfabric:mainfrom
Bechma:add-doc-retrieval
Mar 16, 2026
Merged

add doc retrieval#12
Bechma merged 1 commit intocyberfabric:mainfrom
Bechma:add-doc-retrieval

Conversation

@Bechma
Copy link
Copy Markdown
Collaborator

@Bechma Bechma commented Mar 13, 2026

Summary by CodeRabbit

  • New Features

    • Added a CLI "docs" command to locate and print Rust source across workspace and registry; improved module-path resolution and re-export analysis to show resolved source snippets.
  • Documentation

    • Added a comprehensive CLI skills guide with commands, examples, flows, and quick reference.
  • Dependencies

    • Updated several workspace dependencies and added formatting/parsing and test utilities.
  • CI / Tests

    • Added CI workflow for format/lint/tests and expanded unit and integration tests for resolution.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new docs CLI command that resolves Rust source from workspace metadata or crates.io with caching and re-export traversal; implements module-parser source and metadata resolution APIs and test utilities; updates manifests with new dependencies and test-features; adds SKILLS.md, CI workflow, and rust toolchain config.

Changes

Cohort / File(s) Summary
Workspace Manifest
Cargo.toml
Bumped workspace deps: toml 0.9.12→1.0.6 (serde), toml_edit 0.25.3→0.25.4, notify 8.2.0→8.2 (serde); added prettyplease, proc-macro2, tempfile.
CLI — Docs Command & Wiring
crates/cli/src/docs/mod.rs, crates/cli/src/lib.rs
New docs subcommand (public DocsArgs) with recursive resolution, registry fetch/caching, re-export handling; CLI Commands enum and dispatch extended to call docs.run().
Module Parser — Source Resolution
crates/module-parser/src/source.rs, crates/module-parser/src/lib.rs
New source-resolution module with resolve_rust_path, extract_reexport_target, ResolvedRustPath, NotFoundError; public re-exports added to crate root.
Module Parser — Metadata Integration
crates/module-parser/src/metadata.rs
New metadata utilities: ResolvedMetadataPath, LibraryMapping, resolve_source_from_metadata, list_library_mappings_from_metadata; cargo-metadata parsing and library-target selection logic.
Module Parser — Test Utilities & Features
crates/module-parser/src/test_utils.rs, crates/module-parser/Cargo.toml
Adds TempDirExt test helper; new feature test-utils (optional tempfile) and workspace deps (prettyplease, proc-macro2, tempfile optional).
CLI Dev Dependencies
crates/cli/Cargo.toml
Added dev-dependencies: tempfile (workspace) and module-parser with test-utils feature for tests.
Docs
SKILLS.md
Added comprehensive CyberFabric CLI skills guide and command reference.
Tooling / CI / Toolchain
.github/workflows/ci.yml, rust-toolchain.toml
Added CI workflow (fmt/clippy/tests) and rust-toolchain.toml enabling rustfmt and clippy.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant CLI as CLI (DocsArgs)
    participant Resolver as Resolver
    participant ModuleParser as ModuleParser
    participant Cache as Local Cache
    participant Registry as crates.io
    participant HTTP as HTTP Client

    User->>CLI: Query (e.g., "crate::mod::Item")
    CLI->>Resolver: init(workspace_path, registry, client)
    Resolver->>ModuleParser: try resolve from local metadata
    alt Local metadata found
        ModuleParser->>Resolver: ResolvedMetadataPath
        Resolver->>CLI: return result
    else Not found locally
        Resolver->>Cache: check package cache
        alt Cache hit
            Cache->>Resolver: cached crate root
            Resolver->>ModuleParser: resolve in cached crate
            ModuleParser->>Resolver: ResolvedMetadataPath
            Resolver->>CLI: return result
        else Cache miss
            Resolver->>HTTP: fetch crate info & tarball
            HTTP->>Registry: GET /crates/...
            Registry-->>HTTP: meta & tarball
            HTTP-->>Resolver: download archive
            Resolver->>Cache: extract & cache crate
            Resolver->>ModuleParser: resolve in cached crate
            ModuleParser->>Resolver: ResolvedMetadataPath
            Resolver->>CLI: return result
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop through crates and registry lanes,
I chase re-exports down winding chains,
Cache a tarball, peel layers with care,
I stitch source paths from far‑flung air,
📦🌿📚

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.02% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'add doc retrieval' is related to the main objective of the PR, which introduces a comprehensive docs module for retrieving and resolving Rust documentation sources. However, the title is somewhat vague and doesn't capture the full scope of changes including dependency updates, metadata resolution, and re-export handling.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
crates/module-parser/src/metadata.rs (1)

266-279: Duplicated TempDirExt test helper.

The TempDirExt trait and its implementation are identical in both source.rs and metadata.rs test modules. Consider extracting this to a shared test utilities module to reduce duplication.

♻️ Suggested approach

Create a shared test utility, for example in crates/module-parser/src/test_utils.rs:

#[cfg(test)]
pub(crate) mod test_utils {
    use std::fs;
    use tempfile::TempDir;

    pub trait TempDirExt {
        fn write(&self, relative_path: &str, content: &str);
    }

    impl TempDirExt for TempDir {
        fn write(&self, relative_path: &str, content: &str) {
            let path = self.path().join(relative_path);
            if let Some(parent) = path.parent() {
                fs::create_dir_all(parent).expect("failed to create parent dir");
            }
            fs::write(path, content).expect("failed to write test file");
        }
    }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/module-parser/src/metadata.rs` around lines 266 - 279, Duplicate
TempDirExt test helper found in source.rs and metadata.rs; extract it into a
shared test utility module (e.g., create a cfg(test) pub(crate) mod test_utils)
and move the TempDirExt trait and its impl for tempfile::TempDir (the
write(&self, relative_path: &str, content: &str) method) there; then replace the
duplicated definitions in source.rs and metadata.rs with use of the shared
test_utils (import the trait) so both test modules call TempDirExt::write on
TempDir without duplication.
crates/module-parser/Cargo.toml (1)

17-22: Consider using cargo add for new dependencies.

New dependencies (prettyplease, tempfile) were added by manually editing Cargo.toml. As per coding guidelines: "Always prefer cargo add over manually editing Cargo.toml for adding dependencies."

For future additions:

cargo add prettyplease@0.2.37 -p module-parser
cargo add tempfile@3.27.0 -p module-parser --dev
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/module-parser/Cargo.toml` around lines 17 - 22, The new deps were
added by hand—replace the manual additions of prettyplease = "0.2.37" and
tempfile = "3.27.0" in the module-parser Cargo.toml with additions made via
cargo add; specifically remove the manual entries for prettyplease and tempfile
and run cargo add prettyplease@0.2.37 -p module-parser and cargo add
tempfile@3.27.0 -p module-parser --dev so the dependency metadata and lockfile
are correctly updated (targets: entries named "prettyplease" and the dev-dep
"tempfile").
crates/module-parser/src/source.rs (1)

252-258: Test attribute detection may have false positives.

The is_test_attr function checks if "test" appears anywhere in the token string for #[cfg(...)] or #[cfg_attr(...)] attributes. This could match unintended cases like #[cfg(feature = "contest")] or #[cfg(attestation)].

Consider a more precise check:

♻️ Suggested improvement
 fn is_test_attr(attr: &Attribute) -> bool {
     if attr.path().is_ident("test") {
         return true;
     }

     match &attr.meta {
         Meta::List(list) if list.path.is_ident("cfg") || list.path.is_ident("cfg_attr") => {
-            list.tokens.to_string().contains("test")
+            let tokens = list.tokens.to_string();
+            // Match "test" as a standalone identifier, not as a substring
+            tokens.split(|c: char| !c.is_alphanumeric() && c != '_')
+                .any(|word| word == "test")
         }
         _ => false,
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/module-parser/src/source.rs` around lines 252 - 258, The current
is_test_attr implementation is too fuzzy (it just checks
list.tokens.to_string().contains("test")) and can match substrings like
"contest"; instead parse the Meta::List contents precisely: iterate list.nested
and look for NestedMeta::Meta entries where the inner Meta is either Meta::Path
or Meta::NameValue whose path/ident equals "test", and for cfg_attr handle the
same nested structure; update the match arm that inspects Meta::List (in
is_test_attr) to examine list.nested and return true only when a nested
Meta::Path/NameValue has path.is_ident("test") (avoid stringifying tokens).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/cli/src/docs/mod.rs`:
- Around line 592-600: The current code around
cached_package_versions/cached_versions and the update_latest_symlink call can
race if multiple cyberfabric docs processes run concurrently; to fix, serialize
access when updating the latest symlink by acquiring a process- or file-based
lock on the package_root (or a dedicated lock file in package_root) before
checking latest_link and calling update_latest_symlink, release the lock after
the update, and ensure errors still propagate; alternatively add a comment/doc
note near cached_package_versions/update_latest_symlink documenting the
concurrency limitation if you prefer not to implement locking.
- Around line 414-421: The code in parse_dependency_spec currently uses
Version::parse on the dependency string and silently drops failures (setting
version to None), which loses version requirement info like "^1.0" or ">=1.0";
update parse_dependency_spec to attempt parsing as an exact semver with
Version::parse and, if that fails, parse the string as a semver::VersionReq and
store that requirement (or a new field) so registry fallbacks can use the
requirement; specifically modify the DependencySpec representation or add a
version_req field and change parse_dependency_spec to try
Version::parse(value.as_str()) then fall back to
semver::VersionReq::parse(value.as_str()), preserving either the parsed Version
or VersionReq instead of silently discarding parse errors.

---

Nitpick comments:
In `@crates/module-parser/Cargo.toml`:
- Around line 17-22: The new deps were added by hand—replace the manual
additions of prettyplease = "0.2.37" and tempfile = "3.27.0" in the
module-parser Cargo.toml with additions made via cargo add; specifically remove
the manual entries for prettyplease and tempfile and run cargo add
prettyplease@0.2.37 -p module-parser and cargo add tempfile@3.27.0 -p
module-parser --dev so the dependency metadata and lockfile are correctly
updated (targets: entries named "prettyplease" and the dev-dep "tempfile").

In `@crates/module-parser/src/metadata.rs`:
- Around line 266-279: Duplicate TempDirExt test helper found in source.rs and
metadata.rs; extract it into a shared test utility module (e.g., create a
cfg(test) pub(crate) mod test_utils) and move the TempDirExt trait and its impl
for tempfile::TempDir (the write(&self, relative_path: &str, content: &str)
method) there; then replace the duplicated definitions in source.rs and
metadata.rs with use of the shared test_utils (import the trait) so both test
modules call TempDirExt::write on TempDir without duplication.

In `@crates/module-parser/src/source.rs`:
- Around line 252-258: The current is_test_attr implementation is too fuzzy (it
just checks list.tokens.to_string().contains("test")) and can match substrings
like "contest"; instead parse the Meta::List contents precisely: iterate
list.nested and look for NestedMeta::Meta entries where the inner Meta is either
Meta::Path or Meta::NameValue whose path/ident equals "test", and for cfg_attr
handle the same nested structure; update the match arm that inspects Meta::List
(in is_test_attr) to examine list.nested and return true only when a nested
Meta::Path/NameValue has path.is_ident("test") (avoid stringifying tokens).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1d4887a8-bb1a-4404-94a0-bc3b7e19393c

📥 Commits

Reviewing files that changed from the base of the PR and between dce9cb1 and 6ddc7a5.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • Cargo.toml
  • SKILLS.md
  • crates/cli/src/docs/mod.rs
  • crates/cli/src/lib.rs
  • crates/module-parser/Cargo.toml
  • crates/module-parser/src/lib.rs
  • crates/module-parser/src/metadata.rs
  • crates/module-parser/src/source.rs

@Bechma Bechma force-pushed the add-doc-retrieval branch from 6ddc7a5 to 99fece5 Compare March 16, 2026 11:58
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
crates/module-parser/src/metadata.rs (2)

141-167: Consider simplifying the flat_map iteration.

The current pattern with Vec::new().into_iter() and collect::<Vec<_>>().into_iter() works but is verbose. A filter_map with flatten or using flat_map with Option could be cleaner.

♻️ Suggested simplification
 fn select_library_target(packages: &[Package], name: &str) -> Option<LibraryTarget> {
     let mut candidates = packages
         .iter()
-        .flat_map(|package| {
-            if package.name != name {
-                return Vec::new().into_iter();
-            }
-
-            package
-                .targets
-                .iter()
-                .filter(|target| target.is_lib())
-                .map(move |target| to_library_target(package, target))
-                .collect::<Vec<_>>()
-                .into_iter()
-        })
+        .filter(|package| package.name == name)
+        .flat_map(|package| {
+            package
+                .targets
+                .iter()
+                .filter(|target| target.is_lib())
+                .map(move |target| to_library_target(package, target))
+        })
         .collect::<Vec<_>>();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/module-parser/src/metadata.rs` around lines 141 - 167, The iterator
building in select_library_target is verbose: replace the current
packages.iter().flat_map{... Vec::new().into_iter / collect().into_iter }
pattern with a direct iterator chain that first filters packages by name and
then flat_maps each package's targets into library targets via
to_library_target; e.g., use packages.iter().filter(|p| p.name ==
name).flat_map(|p| p.targets.iter().filter(|t| t.is_lib()).map(|t|
to_library_target(p, t))) to produce candidates before sorting, which removes
the temporary Vec constructions and clarifies intent while keeping the existing
sort and return logic.

63-76: Consider error handling semantics for invalid path segments.

If select_library_target finds the package but resolve_rust_path fails (e.g., the path segments point to a non-existent module), this returns an error rather than None. This might be intentional, but consider whether callers would benefit from distinguishing between "package not found" (None) and "package found but path invalid" (error), or if both should return None.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/module-parser/src/metadata.rs` around lines 63 - 76, The code
currently propagates errors from resolve_rust_path (via the ? after calling it)
which mixes "package found but path invalid" with real errors; change this to
explicitly handle resolve_rust_path failures and return Ok(None) instead of
propagating the error so callers can distinguish "package not found" (None) from
"package found but path invalid" (also None in this model). Concretely, replace
the direct call using ? to a match or map_err-handling around resolve_rust_path
in the block that builds ResolvedMetadataPath (the ResolvedRustPath
destructuring and subsequent Ok(Some(ResolvedMetadataPath { ... }))) so that on
Err(_) you return Ok(None), and on Ok(resolved) you continue constructing and
returning Ok(Some(...)).
crates/module-parser/Cargo.toml (1)

17-23: Consider using workspace dependencies for consistency.

The new dependencies use hardcoded versions while existing dependencies (serde, syn) reference the workspace. For consistency and centralized version management, consider adding these to the workspace Cargo.toml and referencing them with { workspace = true }. (Versions verified: prettyplease@0.2.37, proc-macro2@1.0.106, and tempfile@3.27.0 are all current.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/module-parser/Cargo.toml` around lines 17 - 23, The dependency entries
prettyplease = "0.2.37", proc-macro2 = "1.0.106", and dev-dependency tempfile =
"3.27.0" should be moved into the workspace Cargo.toml and referenced here with
{ workspace = true } for consistency with serde and syn; update the
module-parser/Cargo.toml to replace those versioned lines with prettyplease = {
workspace = true }, proc-macro2 = { workspace = true }, and tempfile = {
workspace = true } (keeping them under [dependencies] or [dev-dependencies] as
appropriate) and add the corresponding entries with the exact versions in the
workspace Cargo.toml.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/cli/src/docs/mod.rs`:
- Around line 610-614: The condition currently compares latest_link (a path like
.../latest) to latest_root (a path like .../<version>), which is always
different; change the comparison to compare the symlink's target basename or
resolved target to the latest version instead. Specifically, when you have
cached_versions.first() -> (latest_version, latest_root), resolve or read the
symlink target stored in latest_link and compare its filename or basename (or
the resolved path's last component) to latest_version.to_string(); only call
update_latest_symlink(&package_root, &latest_version.to_string())? when they
differ. Use the existing symbols cached_versions, latest_link, latest_root,
latest_version, and update_latest_symlink to locate and modify the logic.
- Around line 268-292: The code indexes target_segments[0] without checking for
an empty vector; add an early guard for target_segments.is_empty() before
attempting to index: if empty, construct a NextStep like the other branches
(preferred_path.to_path_buf(), query: build_query(package_name, &[]),
requested_version: None) and return it, otherwise proceed to look up
dependencies.get(&target_segments[0]) and compute remaining_segments and
next_query as before (affecting the logic around parse_dependencies,
resolve_bare_relative_reexport, build_query, and NextStep).
- Around line 1-932: Add a CI workflow that runs on pull requests to enforce
cargo fmt, cargo clippy, and cargo test: create a GitHub Actions job that checks
out the repo, installs the Rust toolchain (stable), runs cargo fmt -- --check,
cargo clippy -- -D warnings, and cargo test (optionally with cached
cargo/registry directories), and fails the run on any non-zero exit so PRs
cannot merge without fixes; include the workflow trigger for pull_request and
optionally push, and ensure the job runs for the crate containing the
DocsArgs/resolve_query_recursive/next_reexport_step code so formatting, lints,
and tests covering those functions are validated automatically.

---

Nitpick comments:
In `@crates/module-parser/Cargo.toml`:
- Around line 17-23: The dependency entries prettyplease = "0.2.37", proc-macro2
= "1.0.106", and dev-dependency tempfile = "3.27.0" should be moved into the
workspace Cargo.toml and referenced here with { workspace = true } for
consistency with serde and syn; update the module-parser/Cargo.toml to replace
those versioned lines with prettyplease = { workspace = true }, proc-macro2 = {
workspace = true }, and tempfile = { workspace = true } (keeping them under
[dependencies] or [dev-dependencies] as appropriate) and add the corresponding
entries with the exact versions in the workspace Cargo.toml.

In `@crates/module-parser/src/metadata.rs`:
- Around line 141-167: The iterator building in select_library_target is
verbose: replace the current packages.iter().flat_map{... Vec::new().into_iter /
collect().into_iter } pattern with a direct iterator chain that first filters
packages by name and then flat_maps each package's targets into library targets
via to_library_target; e.g., use packages.iter().filter(|p| p.name ==
name).flat_map(|p| p.targets.iter().filter(|t| t.is_lib()).map(|t|
to_library_target(p, t))) to produce candidates before sorting, which removes
the temporary Vec constructions and clarifies intent while keeping the existing
sort and return logic.
- Around line 63-76: The code currently propagates errors from resolve_rust_path
(via the ? after calling it) which mixes "package found but path invalid" with
real errors; change this to explicitly handle resolve_rust_path failures and
return Ok(None) instead of propagating the error so callers can distinguish
"package not found" (None) from "package found but path invalid" (also None in
this model). Concretely, replace the direct call using ? to a match or
map_err-handling around resolve_rust_path in the block that builds
ResolvedMetadataPath (the ResolvedRustPath destructuring and subsequent
Ok(Some(ResolvedMetadataPath { ... }))) so that on Err(_) you return Ok(None),
and on Ok(resolved) you continue constructing and returning Ok(Some(...)).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 29980eb5-886d-4b6a-89bb-3367cdcd3e4f

📥 Commits

Reviewing files that changed from the base of the PR and between 6ddc7a5 and 99fece5.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • Cargo.toml
  • SKILLS.md
  • crates/cli/src/docs/mod.rs
  • crates/cli/src/lib.rs
  • crates/module-parser/Cargo.toml
  • crates/module-parser/src/lib.rs
  • crates/module-parser/src/metadata.rs
  • crates/module-parser/src/source.rs
  • crates/module-parser/src/test_utils.rs
🚧 Files skipped from review as they are similar to previous changes (5)
  • Cargo.toml
  • SKILLS.md
  • crates/module-parser/src/source.rs
  • crates/module-parser/src/lib.rs
  • crates/cli/src/lib.rs

@Bechma Bechma force-pushed the add-doc-retrieval branch 2 times, most recently from 4c4c8c0 to c0603ea Compare March 16, 2026 14:33
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
crates/cli/src/docs/mod.rs (1)

322-346: ⚠️ Potential issue | 🟠 Major

Guard against empty re-export targets before indexing.

At Line 323, target_segments[0] can panic if the extracted target is empty. Add an early split_first() guard and branch safely.

Proposed fix
-    let dependencies = parse_dependencies(crate_root)?;
-    let Some(dep) = find_dependency_spec(&dependencies, &target_segments[0]) else {
+    let Some((first_target, remaining_segments)) = target_segments.split_first() else {
+        return Ok(None);
+    };
+
+    let dependencies = parse_dependencies(crate_root)?;
+    let Some(dep) = find_dependency_spec(&dependencies, first_target) else {
         if let Some(next_query) = resolve_bare_relative_reexport(
             crate_root,
             package_name,
             &target_segments,
             &containing_module_segments,
         )? {
             return Ok(Some(NextStep {
                 preferred_path: preferred_path.to_path_buf(),
                 query: next_query,
                 requested_version: None,
             }));
         }

         let next_query = build_query(package_name, &target_segments);
         return Ok(Some(NextStep {
             preferred_path: preferred_path.to_path_buf(),
             query: next_query,
             requested_version: None,
         }));
     };

-    let remaining_segments = &target_segments[1..];
     let next_query = build_query(&dep.package_name, remaining_segments);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/docs/mod.rs` around lines 322 - 346, Replace the direct
indexing of target_segments[0] with a safe split_first() guard: use let
Some((first_segment, remaining_segments)) = target_segments.split_first() else {
return Ok(None); } and then call find_dependency_spec(&dependencies,
first_segment); if find_dependency_spec finds no dependency still call
resolve_bare_relative_reexport(...) as before (using target_segments where
appropriate), and when a dep is found use remaining_segments to build the
next_query via build_query(&dep.package_name, remaining_segments) and construct
the NextStep; this prevents panics when target_segments is empty while keeping
the existing resolve_bare_relative_reexport and build_query logic intact.
🧹 Nitpick comments (2)
crates/module-parser/src/source.rs (1)

83-91: Consider documenting the use-fallback behavior.

The fallback to re-export use items when a direct item isn't found is intentional and tested, but a brief inline comment would help future maintainers understand why re-exports are captured as a fallback rather than matched immediately.

📝 Suggested documentation
+        // If the target name matches a use-item (re-export), capture it as a fallback.
+        // Direct items (modules, structs, etc.) take precedence over re-exports.
         if segments.len() == 1
             && let Item::Use(use_item) = item
             && use_tree_contains_name(&use_item.tree, segment)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/module-parser/src/source.rs` around lines 83 - 91, Add an inline
comment explaining why re-exported `use` items are captured as a fallback rather
than matched immediately: annotate the block that checks segments.len() == 1 &&
let Item::Use(use_item) = item && use_tree_contains_name(&use_item.tree,
segment) (where it sets use_fallback = Some(ResolvedRustPath { source_path:
current_file.to_path_buf(), source: render_item(Item::Use(use_item)) })) to
state that re-exports are recorded only as a fallback to avoid shadowing direct
definitions and to allow preference for direct matches first when resolving
paths.
crates/module-parser/src/metadata.rs (1)

69-71: Consider distinguishing "not found" from resolution errors.

Converting all resolve_rust_path errors to Ok(None) may hide genuine failures (e.g., I/O errors, parse errors). The caller cannot distinguish between "query doesn't match anything" and "resolution failed unexpectedly."

💡 Suggested approach
-    let Ok(resolved) = resolve_rust_path(&library_target.root_source_path, &segments) else {
-        return Ok(None);
-    };
+    let resolved = match resolve_rust_path(&library_target.root_source_path, &segments) {
+        Ok(resolved) => resolved,
+        Err(e) => {
+            // Log at debug level for troubleshooting, but treat as "not found"
+            // since the query may reference items that don't exist in this version
+            eprintln!("debug: path resolution failed: {e}");
+            return Ok(None);
+        }
+    };

Alternatively, propagate errors and let callers decide whether to fall back to other resolution strategies.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/module-parser/src/metadata.rs` around lines 69 - 71, The current code
swallows all errors from resolve_rust_path by turning any Err into Ok(None);
change the handling in metadata.rs so you match on the Result from
resolve_rust_path instead of using let Ok(...) = ..., and only convert a true
"not found" outcome to Ok(None) while propagating other errors (return Err(e) or
use ?). Specifically, inspect the error variant returned by resolve_rust_path
(or update it to return a distinguishable NotFound variant) and in the match for
resolve_rust_path(&library_target.root_source_path, &segments) map NotFound =>
Ok(None) and all other Err cases => propagate the error so callers can decide on
fallback behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/cli/src/docs/mod.rs`:
- Around line 322-346: Replace the direct indexing of target_segments[0] with a
safe split_first() guard: use let Some((first_segment, remaining_segments)) =
target_segments.split_first() else { return Ok(None); } and then call
find_dependency_spec(&dependencies, first_segment); if find_dependency_spec
finds no dependency still call resolve_bare_relative_reexport(...) as before
(using target_segments where appropriate), and when a dep is found use
remaining_segments to build the next_query via build_query(&dep.package_name,
remaining_segments) and construct the NextStep; this prevents panics when
target_segments is empty while keeping the existing
resolve_bare_relative_reexport and build_query logic intact.

---

Nitpick comments:
In `@crates/module-parser/src/metadata.rs`:
- Around line 69-71: The current code swallows all errors from resolve_rust_path
by turning any Err into Ok(None); change the handling in metadata.rs so you
match on the Result from resolve_rust_path instead of using let Ok(...) = ...,
and only convert a true "not found" outcome to Ok(None) while propagating other
errors (return Err(e) or use ?). Specifically, inspect the error variant
returned by resolve_rust_path (or update it to return a distinguishable NotFound
variant) and in the match for
resolve_rust_path(&library_target.root_source_path, &segments) map NotFound =>
Ok(None) and all other Err cases => propagate the error so callers can decide on
fallback behavior.

In `@crates/module-parser/src/source.rs`:
- Around line 83-91: Add an inline comment explaining why re-exported `use`
items are captured as a fallback rather than matched immediately: annotate the
block that checks segments.len() == 1 && let Item::Use(use_item) = item &&
use_tree_contains_name(&use_item.tree, segment) (where it sets use_fallback =
Some(ResolvedRustPath { source_path: current_file.to_path_buf(), source:
render_item(Item::Use(use_item)) })) to state that re-exports are recorded only
as a fallback to avoid shadowing direct definitions and to allow preference for
direct matches first when resolving paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3fc13353-2286-48ab-aede-f0086a66359a

📥 Commits

Reviewing files that changed from the base of the PR and between 99fece5 and c0603ea.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • .github/workflows/ci.yml
  • Cargo.toml
  • SKILLS.md
  • crates/cli/Cargo.toml
  • crates/cli/src/docs/mod.rs
  • crates/cli/src/lib.rs
  • crates/module-parser/Cargo.toml
  • crates/module-parser/src/lib.rs
  • crates/module-parser/src/metadata.rs
  • crates/module-parser/src/source.rs
  • crates/module-parser/src/test_utils.rs
  • rust-toolchain.toml
✅ Files skipped from review due to trivial changes (1)
  • rust-toolchain.toml
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/module-parser/src/lib.rs
  • crates/module-parser/src/test_utils.rs

@Bechma Bechma force-pushed the add-doc-retrieval branch from c0603ea to 1ea8d4f Compare March 16, 2026 15:16
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
crates/cli/src/docs/mod.rs (2)

627-635: Inherit workspace dependency path along with package/version.

parse_dependency_spec_with_workspace currently merges package_name and version but not path. That can degrade preferred-path routing for workspace-inherited path deps.

Proposed patch
     if dependency_uses_workspace_inheritance(value)
         && let Some(workspace_spec) = workspace_deps.and_then(|deps| deps.get(alias))
     {
         if spec.package_name == alias {
             spec.package_name.clone_from(&workspace_spec.package_name);
         }
         if spec.version.is_none() {
             spec.version.clone_from(&workspace_spec.version);
         }
+        if spec.path.is_none() {
+            spec.path.clone_from(&workspace_spec.path);
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/docs/mod.rs` around lines 627 - 635, In
parse_dependency_spec_with_workspace, when
dependency_uses_workspace_inheritance(...) finds a workspace_spec, we currently
clone package_name and version from workspace_spec into spec but omit the path;
add logic to also inherit the path by copying workspace_spec.path into spec.path
when spec.path is None (or empty as appropriate), mirroring how package_name and
version are handled (use the same clone_from call pattern referencing spec.path
and workspace_spec.path) so workspace-inherited dependencies preserve their
preferred path routing.

677-693: Consider bounded retry/backoff for registry HTTP calls.

These requests fail fast on transient 429/5xx/network blips; a small retry policy would improve CLI reliability without changing behavior on hard failures.

Also applies to: 721-730

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/docs/mod.rs` around lines 677 - 693, The registry HTTP call
using client.get(&crate_url).send().await (and the similar block at lines
721–730) should use a bounded retry with exponential backoff for transient
failures: wrap the send()/error_for_status()/json::<ExactCrateResponse>().await
sequence in a retry loop that retries on transient network errors, timeouts,
429, and 5xx responses up to a small max attempts with increasing delay, but
immediately return Ok(None) when response.status() == StatusCode::NOT_FOUND and
propagate hard errors after retries; ensure each failure preserves the original
with_context messages (e.g., "request failed for '{crate_name}'", "registry
returned an error for '{crate_name}'", "invalid crate metadata for
'{crate_name}'") so logs/context remain meaningful.
.github/workflows/ci.yml (1)

15-16: Pin GitHub Actions to immutable SHAs.

Line 15 and Line 16 use floating refs (@v6, @v2). Pinning to commit SHAs improves supply-chain integrity and reproducibility.

#!/bin/bash
# Read-only check: list workflow actions and flag non-SHA refs.
set -euo pipefail

rg -n '^\s*-\s*uses:\s*[^@]+@([^\s]+)' .github/workflows | while IFS=: read -r file line text; do
  ref="$(printf '%s' "$text" | sed -E 's/.*@([[:alnum:]_.-]+).*/\1/')"
  if ! printf '%s' "$ref" | rg -q '^[0-9a-fA-F]{40}$'; then
    printf '%s:%s -> %s\n' "$file" "$line" "$text"
  fi
done
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 15 - 16, Replace the floating refs
used in the workflow (actions/checkout@v6 and Swatinem/rust-cache@v2) with their
immutable commit SHAs: locate the two uses: the string "actions/checkout@v6" and
"Swatinem/rust-cache@v2" in the workflow and update each to use the exact
40-char git commit SHA for the desired release (e.g.,
actions/checkout@<commit-sha>), fetching the correct SHA from the action's
official GitHub repository tags or commit history and verify the checksum before
committing to ensure reproducibility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 15-16: Replace the floating refs used in the workflow
(actions/checkout@v6 and Swatinem/rust-cache@v2) with their immutable commit
SHAs: locate the two uses: the string "actions/checkout@v6" and
"Swatinem/rust-cache@v2" in the workflow and update each to use the exact
40-char git commit SHA for the desired release (e.g.,
actions/checkout@<commit-sha>), fetching the correct SHA from the action's
official GitHub repository tags or commit history and verify the checksum before
committing to ensure reproducibility.

In `@crates/cli/src/docs/mod.rs`:
- Around line 627-635: In parse_dependency_spec_with_workspace, when
dependency_uses_workspace_inheritance(...) finds a workspace_spec, we currently
clone package_name and version from workspace_spec into spec but omit the path;
add logic to also inherit the path by copying workspace_spec.path into spec.path
when spec.path is None (or empty as appropriate), mirroring how package_name and
version are handled (use the same clone_from call pattern referencing spec.path
and workspace_spec.path) so workspace-inherited dependencies preserve their
preferred path routing.
- Around line 677-693: The registry HTTP call using
client.get(&crate_url).send().await (and the similar block at lines 721–730)
should use a bounded retry with exponential backoff for transient failures: wrap
the send()/error_for_status()/json::<ExactCrateResponse>().await sequence in a
retry loop that retries on transient network errors, timeouts, 429, and 5xx
responses up to a small max attempts with increasing delay, but immediately
return Ok(None) when response.status() == StatusCode::NOT_FOUND and propagate
hard errors after retries; ensure each failure preserves the original
with_context messages (e.g., "request failed for '{crate_name}'", "registry
returned an error for '{crate_name}'", "invalid crate metadata for
'{crate_name}'") so logs/context remain meaningful.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d20a0354-d4e7-4c5c-9c8a-f134e0bdc4d3

📥 Commits

Reviewing files that changed from the base of the PR and between c0603ea and 1ea8d4f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • .github/workflows/ci.yml
  • Cargo.toml
  • SKILLS.md
  • crates/cli/Cargo.toml
  • crates/cli/src/docs/mod.rs
  • crates/cli/src/lib.rs
  • crates/module-parser/Cargo.toml
  • crates/module-parser/src/lib.rs
  • crates/module-parser/src/metadata.rs
  • crates/module-parser/src/source.rs
  • crates/module-parser/src/test_utils.rs
  • rust-toolchain.toml
✅ Files skipped from review due to trivial changes (1)
  • rust-toolchain.toml
🚧 Files skipped from review as they are similar to previous changes (6)
  • crates/cli/Cargo.toml
  • crates/module-parser/src/lib.rs
  • crates/module-parser/src/test_utils.rs
  • crates/cli/src/lib.rs
  • crates/module-parser/src/metadata.rs
  • SKILLS.md

Signed-off-by: Bechma <19294519+Bechma@users.noreply.github.com>
@Bechma Bechma force-pushed the add-doc-retrieval branch from 1ea8d4f to 8a0aa57 Compare March 16, 2026 16:26
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/module-parser/src/metadata.rs`:
- Around line 9-13: The public API loses PackageId identity: update the public
structs and selection to preserve exact package identity by either (A) adding a
PackageId field to LibraryMapping and ResolvedMetadataPath so callers can see
the selected identity (modify the definitions of LibraryMapping and
ResolvedMetadataPath and propagate PackageId from the internal LibraryTarget),
or (B) change select_library_target to accept a PackageId and match by exact
identity instead of matching only by package_name + version sort; ensure all
call sites and any construction of LibraryMapping/ResolvedMetadataPath use the
PackageId from LibraryTarget so downstream resolution can unambiguously
reference the chosen package identity.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0ae805f7-a9b1-4891-9dc6-65d7de2602a7

📥 Commits

Reviewing files that changed from the base of the PR and between 1ea8d4f and 8a0aa57.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • .github/workflows/ci.yml
  • Cargo.toml
  • SKILLS.md
  • crates/cli/Cargo.toml
  • crates/cli/src/docs/mod.rs
  • crates/cli/src/lib.rs
  • crates/module-parser/Cargo.toml
  • crates/module-parser/src/lib.rs
  • crates/module-parser/src/metadata.rs
  • crates/module-parser/src/source.rs
  • crates/module-parser/src/test_utils.rs
  • rust-toolchain.toml
✅ Files skipped from review due to trivial changes (1)
  • SKILLS.md
🚧 Files skipped from review as they are similar to previous changes (9)
  • crates/module-parser/src/test_utils.rs
  • crates/cli/src/lib.rs
  • crates/module-parser/Cargo.toml
  • crates/module-parser/src/source.rs
  • rust-toolchain.toml
  • crates/cli/Cargo.toml
  • .github/workflows/ci.yml
  • Cargo.toml
  • crates/cli/src/docs/mod.rs

@Bechma Bechma merged commit 58f9e0c into cyberfabric:main Mar 16, 2026
1 check passed
@Bechma Bechma deleted the add-doc-retrieval branch March 16, 2026 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant