Skip to content

feat(cli): add agentflare code impact - #397

Merged
getappz merged 8 commits into
masterfrom
task/21
Aug 7, 2026
Merged

getappz merged 8 commits into
masterfrom
task/21

Conversation

@getappz

@getappz getappz commented Aug 7, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • New agentflare code impact <path> command: accurate Rust cross-crate blast-radius / test-impact analysis for Cargo workspaces.
  • Fixes a verified bug in lean-ctx (our code-graph MCP server): its Rust import resolution matches by basename only, not crate-qualified path, so it misses same-crate integration tests entirely when two crates in a workspace happen to have same-named files (confirmed live against this repo — crates/agentflare-jobs/tests/supervisor_test.rs's use agentflare_jobs::{Supervisor} produced no edge to supervisor.rs at all).
  • Uses cargo metadata for the authoritative crate graph (including dev-dependencies, so integration tests show up as dependents) plus a use <crate>:: regex confirmation pass (preferring the lean-ctx CLI when installed, falling back to a built-in scan) — no new indexing system.
  • Includes a fix for an over-broad/duplicate-result bug found via live testing after the initial implementation: a crate whose manifest_dir contains other workspace members underneath it (e.g. the root binary crate) was double-reporting those members' own files.

Test plan

  • Unit tests: WorkspaceGraph parsing/resolution, confirm module's lean-ctx-output parser and fallback scanner
  • Integration tests: full temp-workspace resolution, including a regression test for the original basename-collision bug and a regression test for the nested-crate overbreadth bug
  • Live-verified against this repo: agentflare code impact crates/agentflare-jobs/src/supervisor.rs correctly reports supervisor_test.rs, queue_test.rs, worker_test.rs as dependents, with no duplicates

Summary by CodeRabbit

  • New Features
    • Added the code impact command to identify tests and dependent code affected by a target file.
    • Supports individual file paths or newline-separated paths from standard input.
    • Provides formatted text or pretty-printed JSON reports with impact reasons and cross-crate references.
    • Git hook installation now enables background code-index refreshes after commits.
  • Bug Fixes
    • Added clear errors for invalid targets, analysis failures, missing workspace configuration, and files outside the workspace.
    • Improved path ownership and duplicate-result handling across nested workspace crates.
    • Git shipping now warns instead of failing when recording shipment details is unsuccessful.

shiva added 5 commits August 7, 2026 14:27
… an ancestor

A workspace member whose manifest_dir contains other members underneath it
(e.g. the root agentflare binary crate, whose manifest_dir is the repo root)
was reporting those members' own files twice: once correctly attributed,
once again mislabeled as a direct reference from the ancestor crate. Filter
every cross-crate hit through resolve_owner_crate so it only counts toward
the crate that actually owns the file.
@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

Review Change Stack

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 workspace impact analysis for Rust files. The change builds a workspace dependency graph, confirms cross-crate references, exposes shared analysis logic, and adds the code impact CLI command. It also installs a post-commit index refresh hook and records shipped pull requests.

Changes

Code impact analysis

Layer / File(s) Summary
Workspace graph construction
src/code/workspace_graph.rs
Loads Cargo metadata, tracks workspace crates and test targets, resolves file ownership, and traverses reverse dependencies.
Impact report generation
src/code/mod.rs, src/code/confirm.rs, tests/code_impact_test.rs
Builds deduplicated impact reports from own tests and confirmed cross-crate references. Tests cover nested members, ownership, external paths, and missing manifests.
CLI and library exposure
Cargo.toml, src/lib.rs, src/main.rs, src/cli/mod.rs, src/cli/code.rs
Adds the library target and registers code impact, with file or stdin input and text or JSON output.

Git workflow integration

Layer / File(s) Summary
Post-commit hook installation
.githooks/post-commit, src/cli/git.rs
Installs a fail-open post-commit hook that refreshes the code index and call graph in the background.
Ship result recording
src/cli/git.rs
Records shipped pull request details in memory and warns if the best-effort write fails.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant impact_for_path
  participant WorkspaceGraph
  participant search_crate_dir
  CLI->>impact_for_path: analyze target path
  impact_for_path->>WorkspaceGraph: resolve owner and reverse dependents
  WorkspaceGraph-->>impact_for_path: workspace dependency data
  impact_for_path->>search_crate_dir: confirm crate references
  search_crate_dir-->>impact_for_path: matching impact hits
  impact_for_path-->>CLI: ImpactReport
  CLI-->>CLI: render text or JSON
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the new CLI code-impact feature.
Description check ✅ Passed The description explains the change and test coverage, but it omits the template's Notes for reviewers section.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task/21

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with 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.

Inline comments:
In `@src/cli/code.rs`:
- Around line 21-30: Update the ImpactArgs argument definitions and validation
so --stdin cannot be combined with a positional target. Use the clap conflict
configuration or return a usage error before the opts.stdin processing branch,
while preserving standalone --stdin and target behavior.

In `@src/code/confirm.rs`:
- Around line 19-31: Update try_lean_ctx_grep so fallback_scan is triggered only
when spawning lean-ctx fails, while a successfully launched command with a
non-zero exit status is surfaced as a search failure. Preserve the existing
successful-output parsing through parse_lean_ctx_grep_output.

In `@src/code/mod.rs`:
- Around line 47-54: Normalize the path assigned to absolute_target before
passing it to graph.resolve_owner_crate, using lexical normalization rather than
canonicalize so nonexistent targets remain supported. Ensure traversal segments
such as “..” cannot falsely remain under the workspace prefix, and add a
regression test covering a target that escapes its apparent crate directory.

In `@src/code/workspace_graph.rs`:
- Around line 68-70: Preserve Cargo dependency aliases throughout reference
analysis: in src/code/workspace_graph.rs lines 68-70, extend MetaDep and graph
construction to retain each dependency name alongside its package ID and
dependent crate; in src/code/mod.rs lines 86-95, search using each dependent’s
normalized Rust import name rather than only owner.name; in
tests/code_impact_test.rs lines 65-91, add a renamed-dependency fixture and
assert the aliased import reference is reported.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5f16c74a-88b0-4e6c-bf35-352b02abada4

📥 Commits

Reviewing files that changed from the base of the PR and between c20aa47 and df2a44a.

📒 Files selected for processing (9)
  • Cargo.toml
  • src/cli/code.rs
  • src/cli/mod.rs
  • src/code/confirm.rs
  • src/code/mod.rs
  • src/code/workspace_graph.rs
  • src/lib.rs
  • src/main.rs
  • tests/code_impact_test.rs

Comment thread src/cli/code.rs
Comment on lines +21 to +30
pub struct ImpactArgs {
/// File path. Omit when using --stdin.
pub target: Option<PathBuf>,
/// Read a newline-separated file list from stdin (e.g. pipe
/// `git diff --name-only`) and report the union of their impact.
#[arg(long)]
pub stdin: bool,
/// Emit JSON instead of text.
#[arg(long)]
pub json: bool,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/sh
set -eu

printf '%s\n' '== available code-intelligence commands =='
command -v lean-ctx || true
command -v ctx_search || true
command -v ctx_read || true
command -v agentflare || true

printf '%s\n' '== relevant repository files =='
git ls-files | rg '(^|/)src/cli/code\.rs$|(^|/)Cargo\.toml$|(^|/)Cargo\.lock$' || true

printf '%s\n' '== src/cli/code.rs context =='
sed -n '1,120p' src/cli/code.rs

printf '%s\n' '== ImpactArgs and call sites =='
rg -n -C 4 'ImpactArgs|impact|\.stdin|target' src Cargo.toml 2>/dev/null || true

Repository: getappz/agentflare

Length of output: 50375


🏁 Script executed:

#!/bin/sh
set -eu

printf '%s\n' '== relevant CLI definitions =='
rg -n -C 8 'CodeArgs|CodeCommand|ImpactArgs|Impact\(' src/cli src/main.rs src 2>/dev/null \
  | head -n 240

printf '%s\n' '== clap dependency =='
rg -n -C 2 '^clap\s*=|clap::' Cargo.toml Cargo.lock | head -n 120

printf '%s\n' '== exact impact implementation =='
nl -ba src/cli/code.rs | sed -n '15,75p'

printf '%s\n' '== focused static behavior verifier =='
python3 - <<'PY'
from pathlib import Path

p = Path("src/cli/code.rs")
text = p.read_text()

assert "pub target: Option<PathBuf>" in text
assert "#[arg(long)]\n    pub stdin: bool" in text
assert "let targets: Vec<PathBuf> = if opts.stdin {" in text
assert "match opts.target {" in text
stdin_branch = text.split("let targets: Vec<PathBuf> = if opts.stdin {", 1)[1].split(
    "    } else {", 1
)[0]
assert "opts.target" not in stdin_branch

print("Both fields are independently accepted by the declarations.")
print("When opts.stdin is true, the implementation enters the stdin branch.")
print("The stdin branch does not read opts.target; a supplied positional target is ignored.")
PY

Repository: getappz/agentflare

Length of output: 6606


🏁 Script executed:

#!/bin/sh
set -eu

printf '%s\n' '== relevant CLI definitions =='
rg -n -C 8 'CodeArgs|CodeCommand|ImpactArgs|Impact\(' src/cli src/main.rs src 2>/dev/null \
  | head -n 240

printf '%s\n' '== clap dependency =='
rg -n -C 2 '^clap\s*=|clap::' Cargo.toml Cargo.lock | head -n 120

printf '%s\n' '== exact impact implementation =='
nl -ba src/cli/code.rs | sed -n '15,75p'

printf '%s\n' '== focused static behavior verifier =='
python3 - <<'PY'
from pathlib import Path

text = Path("src/cli/code.rs").read_text()

assert "pub target: Option<PathBuf>" in text
assert "#[arg(long)]\n    pub stdin: bool" in text
assert "let targets: Vec<PathBuf> = if opts.stdin {" in text
assert "match opts.target {" in text

stdin_branch = text.split(
    "let targets: Vec<PathBuf> = if opts.stdin {", 1
)[1].split("    } else {", 1)[0]

assert "opts.target" not in stdin_branch

print("Both fields are independently accepted by the declarations.")
print("When opts.stdin is true, the implementation enters the stdin branch.")
print("The stdin branch does not read opts.target; a supplied positional target is ignored.")
PY

Repository: getappz/agentflare

Length of output: 6606


Reject a positional target with --stdin.

When both inputs are present, the opts.stdin branch reads stdin and ignores target. Add conflicting arguments or return a usage error.

🤖 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/cli/code.rs` around lines 21 - 30, Update the ImpactArgs argument
definitions and validation so --stdin cannot be combined with a positional
target. Use the clap conflict configuration or return a usage error before the
opts.stdin processing branch, while preserving standalone --stdin and target
behavior.

Comment thread src/code/confirm.rs
Comment on lines +19 to +31
fn try_lean_ctx_grep(dir: &Path, pattern: &str) -> Option<Vec<ConfirmHit>> {
let output = Command::new("lean-ctx")
.arg("grep")
.arg(pattern)
.arg(dir)
.output()
.ok()?;
if !output.status.success() {
return None;
}
let text = String::from_utf8_lossy(&output.stdout);
Some(parse_lean_ctx_grep_output(&text, dir))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -u
printf '%s\n' '--- intelligence tool availability ---'
command -v ctx_search || true
command -v ctx_callgraph || true
command -v ctx_compose || true
command -v ctx_read || true
command -v ctx_shell || true
printf '%s\n' '--- memory context ---'
if command -v agentflare >/dev/null 2>&1; then
  agentflare memory context || true
else
  printf '%s\n' 'agentflare unavailable'
fi
printf '%s\n' '--- candidate files ---'
git ls-files | grep -E '(^|/)confirm\.rs$|(^|/)Cargo\.toml$|(^|/)README' || true
printf '%s\n' '--- relevant symbols and commands ---'
rg -n --glob '*.rs' --glob '*.toml' --glob '*.md' \
  'try_lean_ctx_grep|parse_lean_ctx_grep_output|lean-ctx|ctx_search|agentflare optimize|agentflare grep|regex scanner' . || true
printf '%s\n' '--- source outline ---'
if command -v ast-grep >/dev/null 2>&1 && [ -f src/code/confirm.rs ]; then
  ast-grep outline src/code/confirm.rs || true
fi
printf '%s\n' '--- source section ---'
if [ -f src/code/confirm.rs ]; then
  sed -n '1,180p' src/code/confirm.rs
fi

Repository: getappz/agentflare

Length of output: 28811


🏁 Script executed:

#!/bin/bash
set -u
printf '%s\n' '--- intelligence tool availability ---'
command -v ctx_search || true
command -v ctx_callgraph || true
command -v ctx_compose || true
command -v ctx_read || true
command -v ctx_shell || true

printf '%s\n' '--- memory context ---'
if command -v agentflare >/dev/null 2>&1; then
  agentflare memory context || true
else
  printf '%s\n' 'agentflare unavailable'
fi

printf '%s\n' '--- relevant files and symbols ---'
git ls-files | grep -E '(^|/)confirm\.rs$|(^|/)Cargo\.toml$|(^|/)README' || true
rg -n --glob '*.rs' --glob '*.toml' --glob '*.md' \
  'try_lean_ctx_grep|parse_lean_ctx_grep_output|lean-ctx|ctx_search|agentflare optimize|regex scanner' . || true

printf '%s\n' '--- source ---'
if [ -f src/code/confirm.rs ]; then
  sed -n '1,180p' src/code/confirm.rs
fi

Repository: getappz/agentflare

Length of output: 28186


🏁 Script executed:

#!/bin/bash
set -u
printf '%s\n' '--- repository guidance ---'
sed -n '31,58p' AGENTS.md
printf '%s\n' '--- code search integration ---'
sed -n '170,230p' src/mcp_server/search.rs
printf '%s\n' '--- search command declarations ---'
rg -n -C 5 'ctx_search|gateway_execute|Command::new\("lean-ctx"\)|\.arg\("grep"\)' \
  src crates AGENTS.md README.md
printf '%s\n' '--- package and history metadata ---'
sed -n '1,120p' Cargo.toml
git status --short
git log --oneline -8 -- src/code/confirm.rs

Repository: getappz/agentflare

Length of output: 19347


🌐 Web query:

yvgude lean-ctx ctx_search CLI grep command documentation

💡 Result:

The command you are asking about is part of LeanCTX (Lean Context), a context engineering layer for AI coding agents [1][2]. In the LeanCTX ecosystem, the CLI command and the corresponding MCP (Model Context Protocol) tool serve different purposes regarding searching your codebase [3][4][5]. CLI Command: lean-ctx grep The CLI command for text-based searching is lean-ctx grep [path] [6][7]. It is designed to perform regex-based searches across your codebase and returns compressed, grouped output [6][3]. It uses the ripgrep utility internally to ensure speed and efficiency [6]. MCP Tool: ctx_search The ctx_search tool is an MCP tool used by AI agents to perform semantic or indexed searches [3][8][4]. Unlike grep, which searches raw files, ctx_search queries an indexed knowledge base using a multi-strategy ranking pipeline (often including Porter stemming, trigram substring matching, and fuzzy correction) [3][9][10]. Key Differences and Usage 1. lean-ctx grep: Use this when you know the exact string, regex, or pattern you are looking for in the files [3]. 2. ctx_search: Use this when you are searching for concepts, or when your AI agent needs to retrieve relevant context from indexed project knowledge without reading raw files into the conversation context [3][9][11]. Note: You may encounter search results referring to a mksglu/context-mode project [9][12]. This is a separate project that also uses tools named ctx_search and ctx_index, but it is distinct from the yvgude/lean-ctx project [1][12]. Always ensure you are consulting the documentation for the specific integration you have installed [5][11].

Citations:


🏁 Script executed:

#!/bin/bash
set -u
printf '%s\n' '--- confirm call sites and module context ---'
rg -n -C 8 'search_crate_dir|mod confirm|pub mod code|code::' src crates
printf '%s\n' '--- gateway implementation and call sites ---'
sed -n '1,90p' src/mcp_server/search.rs
rg -n -C 6 'gateway_execute|Gateway|gateway' src/mcp_server src/code src/cli src/main.rs src/lib.rs
printf '%s\n' '--- code command surface ---'
if [ -f src/code/mod.rs ]; then
  sed -n '1,130p' src/code/mod.rs
fi
printf '%s\n' '--- commit summary ---'
git show --stat --oneline 44f6c81
git show --format= --no-ext-diff 44f6c81 -- src/code/confirm.rs src/code/mod.rs | sed -n '1,240p'

Repository: getappz/agentflare

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -u
python3 - <<'PY'
from pathlib import Path
import subprocess

confirm = Path("src/code/confirm.rs").read_text()
mcp_search = Path("src/mcp_server/search.rs").read_text()

fn = confirm.split("fn try_lean_ctx_grep", 1)[1].split("///", 1)[0]
assert 'Command::new("lean-ctx")' in fn
assert '.arg("grep")' in fn
assert 'ctx_search' not in fn
assert 'gateway_execute("leanctx", "ctx_search"' in mcp_search

# Model the two subprocess outcomes used by the Rust branch:
# spawn failure => output().ok()? returns None; nonzero status => explicit None.
try:
    subprocess.run(
        ["definitely-missing-lean-ctx-for-check"],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        check=False,
    )
except FileNotFoundError:
    spawn_failure_falls_back = True
else:
    spawn_failure_falls_back = False

nonzero = subprocess.run(
    ["sh", "-c", "exit 7"],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    check=False,
)
nonzero_status_falls_back = nonzero.returncode != 0

print({
    "uses_documented_cli": 'Command::new("lean-ctx")' in fn and '.arg("grep")' in fn,
    "ctx_search_is_mcp_gateway_only_in_repository": (
        'ctx_search' not in fn and
        'gateway_execute("leanctx", "ctx_search"' in mcp_search
    ),
    "spawn_failure_falls_back": spawn_failure_falls_back,
    "nonzero_status_falls_back": nonzero_status_falls_back,
})
PY

Repository: getappz/agentflare

Length of output: 309


Handle non-zero lean-ctx grep exits separately. lean-ctx grep is the documented CLI for this synchronous path. The current code also falls back after any non-zero exit, not only when lean-ctx is unavailable. Surface search failures or restrict fallback_scan to spawn failures.

🤖 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/code/confirm.rs` around lines 19 - 31, Update try_lean_ctx_grep so
fallback_scan is triggered only when spawning lean-ctx fails, while a
successfully launched command with a non-zero exit status is surfaced as a
search failure. Preserve the existing successful-output parsing through
parse_lean_ctx_grep_output.

Source: Coding guidelines

Comment thread src/code/mod.rs
Comment on lines +47 to +54
let absolute_target = if target.is_absolute() {
target.to_path_buf()
} else {
repo_root.join(target)
};
let owner = graph
.resolve_owner_crate(&absolute_target)
.ok_or_else(|| ImpactError::NotInWorkspace(absolute_target.clone()))?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- lean-ctx availability ---'
command -v ctx_search || true
command -v ctx_callgraph || true
command -v ctx_compose || true
command -v ctx_read || true
command -v ctx_shell || true

printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(mod\.rs|Cargo\.toml)$|test' | head -200

printf '%s\n' '--- relevant symbols and path checks ---'
rg -n -C 5 'resolve_owner_crate|NotInWorkspace|absolute_target|starts_with|canonicalize|normalize' src tests 2>/dev/null | head -300

Repository: getappz/agentflare

Length of output: 20878


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- src/code/mod.rs outline and implementation ---'
ast-grep outline src/code/mod.rs 2>/dev/null || true
sed -n '1,180p' src/code/mod.rs

printf '%s\n' '--- owner-resolution definitions and callers ---'
rg -n -C 12 'resolve_owner_crate|struct .*Graph|impl .*Graph|manifest_dir|owner_crate' src tests crates 2>/dev/null | head -500

printf '%s\n' '--- impact tests ---'
sed -n '1,260p' tests/code_impact_test.rs

Repository: getappz/agentflare

Length of output: 36700


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- path construction and impact callers ---'
rg -n -C 10 'impact_for_path\(|PathBuf|canonicalize\(|normalize|code impact' src tests | head -500

printf '%s\n' '--- path semantics probe ---'
python3 - <<'PY'
from pathlib import PurePath

root = PurePath('/workspace')
raw = root / 'crates/jobs/../../outside.rs'
owner = root / 'crates/jobs'
manifest_other = root / 'crates/top'

def lexical_components(path):
    return path.parts

print('raw:', raw)
print('raw components:', lexical_components(raw))
print('raw starts_with jobs:', lexical_components(raw)[:len(owner.parts)] == owner.parts)
print('raw starts_with top:', lexical_components(raw)[:len(manifest_other.parts)] == manifest_other.parts)

parts = []
for part in raw.parts:
    if part == '..':
        if parts and parts[-1] != '/':
            parts.pop()
    elif part != '.':
        parts.append(part)
normalized = PurePath(*parts)
print('normalized:', normalized)
print('normalized starts_with jobs:', lexical_components(normalized)[:len(owner.parts)] == owner.parts)
print('normalized starts_with top:', lexical_components(normalized)[:len(manifest_other.parts)] == manifest_other.parts)
PY

printf '%s\n' '--- graph path origins ---'
sed -n '72,165p' src/code/workspace_graph.rs

Repository: getappz/agentflare

Length of output: 31521


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- code impact CLI path inputs ---'
fd -i 'code.rs|mod.rs' src/cli src/code --type f -x sh -c 'echo "--- $1"; rg -n -C 8 "impact_for_path|PathBuf|target|repo_root|current_dir" "$1"' sh {}

printf '%s\n' '--- all direct callers ---'
rg -n -C 8 'impact_for_path' --glob '*.rs' .

printf '%s\n' '--- Cargo metadata path assumptions in repository text ---'
rg -n -C 5 'manifest_path|src_path|cargo metadata|repo_root' src/code tests/code_impact_test.rs

Repository: getappz/agentflare

Length of output: 6901


Normalize absolute_target before resolving its owner.

Path::starts_with is lexical, so crates/jobs/../../outside.rs still matches crates/jobs and can produce a false impact report. Use lexical normalization instead of canonicalize, which fails for nonexistent targets. Add a regression test.

🤖 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/code/mod.rs` around lines 47 - 54, Normalize the path assigned to
absolute_target before passing it to graph.resolve_owner_crate, using lexical
normalization rather than canonicalize so nonexistent targets remain supported.
Ensure traversal segments such as “..” cannot falsely remain under the workspace
prefix, and add a regression test covering a target that escapes its apparent
crate directory.

Comment on lines +68 to +70
struct MetaDep {
pkg: String,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For Cargo metadata format version 1, how are renamed dependencies represented in resolve.nodes[].deps, and does the name field identify the Rust import crate name?

💡 Result:

In Cargo metadata format version 1, renamed dependencies are represented within the resolve.nodes[].deps array [1][2]. The resolve.nodes[].deps field is an array of objects that provides detailed information about a node's dependencies, serving as an alternative to the simpler dependencies field (which only lists opaque PackageId strings and does not support renamed dependencies) [3][1][2]. Each object in the deps array contains a name field, which explicitly holds the name by which the dependency is imported (the "extern crate" name) [4][1][2]. Yes, the name field identifies the Rust import crate name [4]. Specifically: - If a dependency has been renamed (e.g., using package = "..." in Cargo.toml), the name field contains the new, renamed name rather than the original package name [1][2]. - If the dependency has not been renamed, it contains the default library target name (often the package name with hyphens replaced by underscores) [4][1]. As such, this name field reflects the identifier that should be used in Rust code (e.g., extern crate ...; or in use ...; statements) to refer to that dependency [4]. Note that this name is the library target name, not the package's registration name [4][1].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' 'lean-ctx availability:'
command -v ctx_search || true
command -v ctx_read || true
command -v ctx_callgraph || true
command -v ctx_compose || true
command -v ctx_shell || true
printf '%s\n' 'Relevant files:'
git ls-files 'src/code/workspace_graph.rs' 'src/code/mod.rs' 'tests/code_impact_test.rs' 'Cargo.toml'
printf '%s\n' 'workspace_graph.rs outline/context:'
if command -v ast-grep >/dev/null 2>&1; then
  ast-grep outline src/code/workspace_graph.rs || true
fi
sed -n '1,180p' src/code/workspace_graph.rs
printf '%s\n' 'mod.rs relevant context:'
sed -n '1,150p' src/code/mod.rs
printf '%s\n' 'impact test context:'
sed -n '1,140p' tests/code_impact_test.rs

Repository: getappz/agentflare

Length of output: 15967


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' 'MetaDep and graph API usages:'
rg -n -C 3 'MetaDep|reverse_dependents|crate_ident|search_crate_dir' src tests
printf '%s\n' 'Remaining workspace_graph.rs tests:'
sed -n '150,300p' src/code/workspace_graph.rs
printf '%s\n' 'Confirmation search implementation:'
fd -i -t f 'confirm' src
confirm_file="$(fd -i -t f 'confirm' src | head -n 1)"
if [ -n "${confirm_file}" ]; then
  sed -n '1,220p' "$confirm_file"
fi
printf '%s\n' 'Synthetic Cargo metadata probe:'
python3 - <<'PY'
import json

metadata = {
    "packages": [
        {"id": "path+file:///jobs#0.1.0", "name": "jobs"},
        {"id": "path+file:///app#0.1.0", "name": "app"},
    ],
    "workspace_members": [
        "path+file:///jobs#0.1.0",
        "path+file:///app#0.1.0",
    ],
    "resolve": {"nodes": [
        {"id": "path+file:///jobs#0.1.0", "deps": []},
        {"id": "path+file:///app#0.1.0", "deps": [
            {"name": "jobs_alias", "pkg": "path+file:///jobs#0.1.0",
             "dep_kinds": [{"kind": None, "target": None}]}
        ]},
    ]},
}
node = next(n for n in metadata["resolve"]["nodes"] if n["id"].endswith("/app#0.1.0"))
dep = node["deps"][0]
print("dependency package:", dep["pkg"])
print("dependency import name:", dep["name"])
print("current search identifier:", "jobs".replace("-", "_"))
print("alias search identifier:", dep["name"].replace("-", "_"))
print("current search finds alias reference:", "jobs_alias::Supervisor".find("jobs") >= 0)
print("alias search finds alias reference:", "jobs_alias::Supervisor".find(dep["name"]) >= 0)
PY

Repository: getappz/agentflare

Length of output: 15654


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
import json
import re

metadata = {
    "packages": [
        {"id": "path+file:///jobs#0.1.0", "name": "jobs"},
        {"id": "path+file:///app#0.1.0", "name": "app"},
    ],
    "workspace_members": [
        "path+file:///jobs#0.1.0",
        "path+file:///app#0.1.0",
    ],
    "resolve": {"nodes": [
        {"id": "path+file:///jobs#0.1.0", "deps": []},
        {"id": "path+file:///app#0.1.0", "deps": [
            {"name": "jobs_alias", "pkg": "path+file:///jobs#0.1.0",
             "dep_kinds": [{"kind": None, "target": None}]}
        ]},
    ]},
}
package_names = {p["id"]: p["name"] for p in metadata["packages"]}
owner = "jobs"
node = next(n for n in metadata["resolve"]["nodes"]
            if package_names[n["id"]] == "app")
dep = next(d for d in node["deps"] if package_names[d["pkg"]] == owner)
source_line = "use jobs_alias::Supervisor;"
current_ident = owner.replace("-", "_")
alias_ident = dep["name"].replace("-", "_")
current_pattern = re.compile(r"\b" + re.escape(current_ident) + r"::")
alias_pattern = re.compile(r"\b" + re.escape(alias_ident) + r"::")
print("metadata deps.name:", dep["name"])
print("current confirm pattern:", current_pattern.pattern)
print("alias confirm pattern:", alias_pattern.pattern)
print("current pattern matches source:", bool(current_pattern.search(source_line)))
print("alias pattern matches source:", bool(alias_pattern.search(source_line)))
assert not current_pattern.search(source_line)
assert alias_pattern.search(source_line)
PY

Repository: getappz/agentflare

Length of output: 332


Preserve Cargo dependency aliases for reference confirmation.

resolve.nodes[].deps[].name stores the dependency import name. The graph discards this field and searches only owner.name, so use jobs_alias::Supervisor; is not reported for jobs_alias = { package = "jobs", ... }.

  • src/code/workspace_graph.rs#L68-L70: Retain each dependency name with its package ID and dependent crate.
  • src/code/mod.rs#L86-L95: Search each dependent's import name, normalized for Rust identifiers.
  • tests/code_impact_test.rs#L65-L91: Add a renamed-dependency fixture and assert that its reference is reported.
📍 Affects 3 files
  • src/code/workspace_graph.rs#L68-L70 (this comment)
  • src/code/mod.rs#L86-L95
  • tests/code_impact_test.rs#L65-L91
🤖 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/code/workspace_graph.rs` around lines 68 - 70, Preserve Cargo dependency
aliases throughout reference analysis: in src/code/workspace_graph.rs lines
68-70, extend MetaDep and graph construction to retain each dependency name
alongside its package ID and dependent crate; in src/code/mod.rs lines 86-95,
search using each dependent’s normalized Rust import name rather than only
owner.name; in tests/code_impact_test.rs lines 65-91, add a renamed-dependency
fixture and assert the aliased import reference is reported.

shiva added 2 commits August 7, 2026 18:13
Adds post-commit to the installed hook set (.githooks/, HOOKS in
src/cli/git.rs): runs `lean-ctx index build` + `lean-ctx graph build`
detached in the background after every commit, fail-open if lean-ctx
isn't on PATH. Keeps ctx_search/ctx_callgraph and agentflare code
impact's reference confirmation from running against a stale graph,
without a watcher process or new infra.
Closes the gap surfaced this session: PR #397's own existence was
undiscoverable except via git/gh archaeology across branches, because
nothing wrote it to memory. `agentflare git ship` now calls
memory::mcp::handle_remember after the PR is created/found (topic_key
pr-<number>, so a re-run updates rather than duplicates), so a later
session can `memory recall` what shipped instead of re-discovering it.
Best-effort: a remember failure only warns, never fails an already-
successful ship.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with 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.

Inline comments:
In @.githooks/post-commit:
- Line 19: Update the post-commit refresh command to avoid invoking lean-ctx
graph build redundantly or concurrently with lean-ctx index build. Use the
single atomic index refresh path, or chain separate builds with &&, and preserve
failure information in a durable diagnostic instead of discarding statuses
through background redirection.

In `@src/cli/git.rs`:
- Around line 1007-1008: Update the content construction near default_pr_body so
it always explicitly includes the exact commit range {base}..{head}, regardless
of whether the log contains commit subjects or uses the empty-log fallback.
Preserve the existing PR URL in the persisted content.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 986b1661-075c-4349-85d9-1e0dd2263bbe

📥 Commits

Reviewing files that changed from the base of the PR and between df2a44a and fdb2e03.

📒 Files selected for processing (2)
  • .githooks/post-commit
  • src/cli/git.rs

Comment thread .githooks/post-commit
# copy under ~/.agentflare/githooks/).

if command -v lean-ctx >/dev/null 2>&1; then
( lean-ctx index build >/dev/null 2>&1; lean-ctx graph build >/dev/null 2>&1 ) &

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- .githooks/post-commit ---'
cat -n .githooks/post-commit

printf '%s\n' '--- lean-ctx references ---'
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' \
  'lean-ctx|index build|graph build' .

Repository: getappz/agentflare

Length of output: 20312


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- integration guidance ---'
cat -n AGENTS.md | sed -n '31,60p'
cat -n src/cli/git.rs | sed -n '410,445p'

printf '%s\n' '--- hook installation and canonical-copy references ---'
rg -n -C 5 --hidden \
  'post-commit|githooks|canonical|code index refresh' \
  src .githooks README.md docs-site AGENTS.md

printf '%s\n' '--- upstream lean-ctx command references ---'
curl -L --fail --silent --show-error \
  https://api.github.com/repos/yvgude/lean-ctx/contents \
  | jq -r '.[].name' | head -80

Repository: getappz/agentflare

Length of output: 23130


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- upstream documentation ---'
for path in README.md LEAN-CTX.md ARCHITECTURE.md; do
  printf '\n### %s\n' "$path"
  curl -L --fail --silent --show-error \
    "https://raw.githubusercontent.com/yvgude/lean-ctx/main/$path" \
    | rg -n -i -C 4 'index build|graph build|call.?graph|atomic|generation|refresh' || true
done

printf '%s\n' '--- upstream source paths related to index and graph ---'
curl -L --fail --silent --show-error \
  'https://api.github.com/repos/yvgude/lean-ctx/git/trees/main?recursive=1' \
  | jq -r '.tree[] | select(.type == "blob") | .path' \
  | rg -i '(^|/)(index|graph|call.?graph)|index|graph' \
  | head -160

Repository: getappz/agentflare

Length of output: 10416


🏁 Script executed:

#!/bin/bash
set -eu

base='https://raw.githubusercontent.com/yvgude/lean-ctx/main'

for path in rust/src/cli/index_cmd.rs rust/src/cli/mod.rs rust/src/tools/ctx_index.rs rust/src/tools/ctx_graph.rs rust/src/core/index_orchestrator.rs rust/src/core/graph_coordinator.rs rust/src/core/index_paths.rs rust/src/core/index_bundle.rs; do
  printf '\n--- %s ---\n' "$path"
  curl -L --fail --silent --show-error "$base/$path" \
    | rg -n -i -C 5 'Build|build|snapshot|generation|atomic|rename|lock|graph' \
    | head -220 || true
done

printf '\n--- exact command declarations and dispatch references ---\n'
curl -L --fail --silent --show-error \
  'https://api.github.com/repos/yvgude/lean-ctx/git/trees/main?recursive=1' \
  | jq -r '.tree[] | select(.type == "blob") | .path' \
  | rg 'rust/src/cli|rust/src/tools' \
  | while read -r path; do
      content="$(curl -L --fail --silent --show-error "$base/$path" || true)"
      if printf '%s' "$content" | rg -q -i 'graph build|index build|Build'; then
        printf '%s\n' "$path"
        printf '%s' "$content" | rg -n -i -C 2 'graph build|index build|Build' | head -60
      fi
    done

Repository: getappz/agentflare

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -eu

base='https://raw.githubusercontent.com/yvgude/lean-ctx/main'

printf '%s\n' '--- graph build command ---'
curl -L --fail --silent --show-error \
  "$base/rust/src/cli/dispatch/analytics/graph.rs" \
  | sed -n '1,115p'

printf '%s\n' '--- index orchestration build entry points ---'
curl -L --fail --silent --show-error \
  "$base/rust/src/core/index_orchestrator.rs" \
  | sed -n '250,520p'

printf '%s\n' '--- graph index scan and persistence ---'
curl -L --fail --silent --show-error \
  "$base/rust/src/core/graph_index/mod.rs" \
  | rg -n -C 8 'pub fn scan|fn scan|save|persist|rename|lock|ProjectIndex'

printf '%s\n' '--- deterministic shell sequencing probe ---'
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
cat >"$tmpdir/fake-lean-ctx" <<'EOF'
#!/usr/bin/env bash
if [[ "$*" == "index build" ]]; then
    printf '%s\n' index >>"$LOG"
    exit 7
fi
if [[ "$*" == "graph build" ]]; then
    printf '%s\n' graph >>"$LOG"
    exit 0
fi
exit 99
EOF
chmod +x "$tmpdir/fake-lean-ctx"
export PATH="$tmpdir:$PATH" LOG="$tmpdir/log"
( lean-ctx index build >/dev/null 2>&1; lean-ctx graph build >/dev/null 2>&1 )
printf 'semicolon_status=%s commands=%s\n' "$?" "$(paste -sd, "$tmpdir/log")"
: >"$tmpdir/log"
( lean-ctx index build >/dev/null 2>&1 && lean-ctx graph build >/dev/null 2>&1 ) || true
printf 'and_status=%s commands=%s\n' "$?" "$(paste -sd, "$tmpdir/log")"

Repository: getappz/agentflare

Length of output: 34038


🏁 Script executed:

#!/bin/bash
set -eu

tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT

cat >"$tmpdir/lean-ctx" <<'EOF'
#!/usr/bin/env bash
if [[ "$*" == "index build" ]]; then
    printf '%s\n' index >>"$LOG"
    exit 7
fi
if [[ "$*" == "graph build" ]]; then
    printf '%s\n' graph >>"$LOG"
    exit 0
fi
exit 99
EOF
chmod +x "$tmpdir/lean-ctx"
export PATH="$tmpdir:$PATH" LOG="$tmpdir/log"

set +e
( lean-ctx index build >/dev/null 2>&1; lean-ctx graph build >/dev/null 2>&1 )
semicolon_status=$?
semicolon_commands="$(paste -sd, "$tmpdir/log")"

: >"$tmpdir/log"
( lean-ctx index build >/dev/null 2>&1 && lean-ctx graph build >/dev/null 2>&1 )
and_status=$?
and_commands="$(paste -sd, "$tmpdir/log")"
set -e

printf 'semicolon_status=%s commands=%s\n' "$semicolon_status" "$semicolon_commands"
printf 'and_status=%s commands=%s\n' "$and_status" "$and_commands"

Repository: getappz/agentflare

Length of output: 288


Serialize the code-index refresh and record failures.

lean-ctx index build already builds the graph and BM25 indexes. Running lean-ctx graph build separately can race with that graph build. The ; also runs it after an index failure, while redirected statuses make the background refresh report success. Use one atomic refresh path, or chain separate commands with && and write failures to a durable diagnostic.

🤖 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 @.githooks/post-commit at line 19, Update the post-commit refresh command to
avoid invoking lean-ctx graph build redundantly or concurrently with lean-ctx
index build. Use the single atomic index refresh path, or chain separate builds
with &&, and preserve failure information in a durable diagnostic instead of
discarding statuses through background redirection.

Comment thread src/cli/git.rs
Comment on lines +1007 to +1008
let log = default_pr_body(repo_root, base, head);
let content = format!("{log}\n\n{}", pr.html_url);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Persist the exact commit range.

When default_pr_body returns commit subjects, it does not include base or head. Only the empty-log fallback contains the range. The memory record then cannot identify the exact shipped range. Include {base}..{head} explicitly in content.

Proposed fix
     let log = default_pr_body(repo_root, base, head);
-    let content = format!("{log}\n\n{}", pr.html_url);
+    let content = format!(
+        "Commit range: {base}..{head}\n\n{log}\n\n{}",
+        pr.html_url
+    );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let log = default_pr_body(repo_root, base, head);
let content = format!("{log}\n\n{}", pr.html_url);
let log = default_pr_body(repo_root, base, head);
let content = format!(
"Commit range: {base}..{head}\n\n{log}\n\n{}",
pr.html_url
);
🤖 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/cli/git.rs` around lines 1007 - 1008, Update the content construction
near default_pr_body so it always explicitly includes the exact commit range
{base}..{head}, regardless of whether the log contains commit subjects or uses
the empty-log fallback. Preserve the existing PR URL in the persisted content.

CI's rustfmt (1.97.1) collapses this fs::write call onto one line;
local default toolchain was 1.97.0, which didn't flag it -- a rustfmt
patch-version formatting difference, not new code. Fixes the fmt/CI
Green failures blocking #397.
@getappz
getappz merged commit 31c978d into master Aug 7, 2026
15 checks passed
@getappz
getappz deleted the task/21 branch August 7, 2026 13:10
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