Skip to content

Commit e8d05f7

Browse files
committed
Fix CLI indexer bugs and build warnings
- Fix glob pattern matching by using relative paths for pattern comparison - Fix vars file naming (.acp.vars.json instead of .acp.cache.vars.json) - Enhance parser to extract @acp: annotations and build symbols - Fix config resolution when indexing subdirectories - Remove unused cfg attribute and prefix unused variables with underscore
1 parent 3463e68 commit e8d05f7

5 files changed

Lines changed: 183 additions & 22 deletions

File tree

cli/src/config/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,7 @@ fn default_workers() -> usize {
189189
num_cpus::get().max(1)
190190
}
191191

192-
// Fallback if num_cpus not available
193-
#[cfg(not(feature = "num_cpus"))]
192+
// Fallback mock for num_cpus
194193
mod num_cpus {
195194
pub fn get() -> usize {
196195
4

cli/src/index/indexer.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,30 @@ impl Indexer {
112112
.into_iter()
113113
.filter_map(|e| e.ok())
114114
.filter(|e| e.file_type().is_file())
115-
.map(|e| e.path().to_string_lossy().to_string())
116-
.filter(|path| {
115+
.filter_map(|e| {
116+
// Get path relative to root for pattern matching
117+
let full_path = e.path().to_string_lossy().to_string();
118+
let relative_path = e.path()
119+
.strip_prefix(root)
120+
.map(|p| p.to_string_lossy().to_string())
121+
.unwrap_or_else(|_| full_path.clone());
122+
117123
// Must match at least one include pattern
118-
let included = include_patterns.iter().any(|p| p.matches(path));
124+
let match_opts = glob::MatchOptions {
125+
case_sensitive: true,
126+
require_literal_separator: false,
127+
require_literal_leading_dot: false,
128+
};
129+
let included = include_patterns.is_empty() ||
130+
include_patterns.iter().any(|p| p.matches_with(&relative_path, match_opts));
119131
// Must not match any exclude pattern
120-
let excluded = exclude_patterns.iter().any(|p| p.matches(path));
121-
included && !excluded
132+
let excluded = exclude_patterns.iter().any(|p| p.matches_with(&relative_path, match_opts));
133+
134+
if included && !excluded {
135+
Some(full_path)
136+
} else {
137+
None
138+
}
122139
})
123140
.collect();
124141

cli/src/main.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,24 @@ async fn main() -> anyhow::Result<()> {
273273
match cli.command {
274274
Commands::Index { root, output, vars } => {
275275
println!("{} Indexing codebase...", style("→").cyan());
276-
276+
277+
// Use config from target root if it exists, otherwise use defaults
278+
// This avoids pattern mismatches when indexing a subdirectory
279+
let config = {
280+
let root_config = root.join(".acp.config.json");
281+
let root_str = root.to_string_lossy();
282+
if root_config.exists() {
283+
// Config in target directory takes precedence
284+
Config::load(&root_config).unwrap_or_default()
285+
} else if root_str != "." && root_str != "./" {
286+
// Indexing a subdirectory - use defaults to avoid pattern mismatches
287+
// (parent's "src/**/*" won't match files when root IS src/)
288+
Config::default()
289+
} else {
290+
config
291+
}
292+
};
293+
277294
let indexer = Indexer::new(config)?;
278295
let cache = indexer.index(&root).await?;
279296

@@ -285,7 +302,13 @@ async fn main() -> anyhow::Result<()> {
285302

286303
if vars {
287304
let vars_file = indexer.generate_vars(&cache);
288-
let vars_path = output.with_extension("vars.json");
305+
// Replace .cache.json with .vars.json (or append .vars.json if different pattern)
306+
let output_str = output.to_string_lossy();
307+
let vars_path = if output_str.contains(".cache.json") {
308+
PathBuf::from(output_str.replace(".cache.json", ".vars.json"))
309+
} else {
310+
output.with_extension("vars.json")
311+
};
289312
vars_file.write_json(&vars_path)?;
290313
println!("{} Vars written to {}", style("✓").green(), vars_path.display());
291314
}

cli/src/parse/mod.rs

Lines changed: 133 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,39 +61,123 @@ impl Parser {
6161
pub fn parse<P: AsRef<Path>>(&self, path: P) -> Result<ParseResult> {
6262
let path = path.as_ref();
6363
let content = std::fs::read_to_string(path)?;
64-
let lang = Language::from_path(path)
64+
let _lang = Language::from_path(path)
6565
.ok_or_else(|| AcpError::UnsupportedLanguage(
6666
path.extension()
6767
.map(|e| e.to_string_lossy().to_string())
6868
.unwrap_or_default()
6969
))?;
7070

71-
// TODO: Implement actual tree-sitter parsing
72-
// For now, return a basic result with file metadata
7371
let lines = content.lines().count();
7472
let file_name = path.file_stem()
7573
.map(|s| s.to_string_lossy().to_string())
7674
.unwrap_or_default();
75+
let file_path = path.to_string_lossy().to_string();
76+
77+
// Parse @acp: annotations from source
78+
let annotations = self.parse_annotations(&content);
79+
80+
// Extract file-level metadata from annotations
81+
let mut module_name = file_name.clone();
82+
let mut domains = vec![];
83+
let mut layer = None;
84+
let mut symbols = vec![];
85+
let mut symbol_names = vec![];
86+
let mut calls = vec![];
87+
88+
// Track current symbol context for multi-line annotations
89+
let mut current_symbol: Option<SymbolBuilder> = None;
90+
91+
for ann in &annotations {
92+
match ann.name.as_str() {
93+
"module" => {
94+
if let Some(val) = &ann.value {
95+
module_name = val.trim_matches('"').to_string();
96+
}
97+
}
98+
"domain" => {
99+
if let Some(val) = &ann.value {
100+
domains.push(val.trim_matches('"').to_string());
101+
}
102+
}
103+
"layer" => {
104+
if let Some(val) = &ann.value {
105+
layer = Some(val.trim_matches('"').to_string());
106+
}
107+
}
108+
"symbol" => {
109+
// Save previous symbol if exists
110+
if let Some(builder) = current_symbol.take() {
111+
let sym = builder.build(&file_path);
112+
symbol_names.push(sym.name.clone());
113+
symbols.push(sym);
114+
}
115+
// Start new symbol
116+
if let Some(val) = &ann.value {
117+
current_symbol = Some(SymbolBuilder::new(
118+
val.trim_matches('"').to_string(),
119+
ann.line,
120+
));
121+
}
122+
}
123+
"summary" => {
124+
if let Some(ref mut builder) = current_symbol {
125+
if let Some(val) = &ann.value {
126+
builder.summary = Some(val.trim_matches('"').to_string());
127+
}
128+
}
129+
}
130+
"calls" => {
131+
if let Some(ref mut builder) = current_symbol {
132+
if let Some(val) = &ann.value {
133+
let callees: Vec<String> = val
134+
.split(',')
135+
.map(|s| s.trim().trim_matches('"').to_string())
136+
.collect();
137+
builder.calls.extend(callees);
138+
}
139+
}
140+
}
141+
_ => {}
142+
}
143+
}
144+
145+
// Save last symbol
146+
if let Some(builder) = current_symbol {
147+
let sym = builder.build(&file_path);
148+
if !sym.calls.is_empty() {
149+
calls.push((sym.name.clone(), sym.calls.clone()));
150+
}
151+
symbol_names.push(sym.name.clone());
152+
symbols.push(sym);
153+
}
154+
155+
// Build call edges for earlier symbols
156+
for sym in &symbols {
157+
if !sym.calls.is_empty() {
158+
calls.push((sym.name.clone(), sym.calls.clone()));
159+
}
160+
}
77161

78162
let file = FileEntry {
79-
path: path.to_string_lossy().to_string(),
80-
module: file_name.clone(),
163+
path: file_path,
164+
module: module_name,
81165
lines,
82-
domains: vec![],
83-
layer: None,
166+
domains,
167+
layer,
84168
stability: Stability::Active,
85169
depends: vec![],
86-
exports: vec![],
87-
symbols: vec![],
170+
exports: symbol_names.clone(),
171+
symbols: symbol_names,
88172
keywords: vec![],
89173
hash: Some(format!("{:x}", md5::compute(&content))),
90174
guardrails: None,
91175
};
92176

93177
Ok(ParseResult {
94178
file,
95-
symbols: vec![],
96-
calls: vec![],
179+
symbols,
180+
calls,
97181
})
98182
}
99183

@@ -128,4 +212,42 @@ pub struct Annotation {
128212
pub name: String,
129213
pub value: Option<String>,
130214
pub line: usize,
215+
}
216+
217+
/// Helper to build SymbolEntry from annotations
218+
struct SymbolBuilder {
219+
name: String,
220+
line: usize,
221+
summary: Option<String>,
222+
calls: Vec<String>,
223+
}
224+
225+
impl SymbolBuilder {
226+
fn new(name: String, line: usize) -> Self {
227+
Self {
228+
name,
229+
line,
230+
summary: None,
231+
calls: vec![],
232+
}
233+
}
234+
235+
fn build(self, file_path: &str) -> SymbolEntry {
236+
SymbolEntry {
237+
name: self.name,
238+
fqn: None,
239+
symbol_type: SymbolType::Fn,
240+
file: file_path.to_string(),
241+
lines: [self.line, self.line + 10], // Approximate
242+
summary: self.summary,
243+
signature: None,
244+
exported: true,
245+
async_fn: false,
246+
calls: self.calls,
247+
throws: vec![],
248+
flags: vec![],
249+
side_effects: vec![],
250+
complexity: None,
251+
}
252+
}
131253
}

cli/src/watch.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ use crate::error::Result;
1515

1616
/// File watcher for incremental updates
1717
pub struct FileWatcher {
18-
config: AcpConfig,
18+
_config: AcpConfig,
1919
}
2020

2121
impl FileWatcher {
2222
pub fn new(config: AcpConfig) -> Self {
23-
Self { config }
23+
Self { _config: config }
2424
}
2525

2626
/// Start watching for changes

0 commit comments

Comments
 (0)