Skip to content

Commit af0af06

Browse files
invalidclaude
andcommitted
feat(v0.2.6): add const/variable cross-reference tracking and same-file usage detection
- Add const_item/static_item to Rust extract_exports, const_spec/var_spec to Go - Track all variables (not just exported) for same-file usage in scanner - Split query output into usedAt (local) and importedBy (cross-file) sections - Fix Python export fallback dedup, fix format_symbol_results display for local refs - Bump version to 0.2.6 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 888f5cb commit af0af06

33 files changed

+1741
-688
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ AST-based code graph mapping plugin for [Claude Code](https://docs.anthropic.com
1111
- **多语言支持 / Multi-Language** — TypeScript, JavaScript, Python, Go, Rust, Java, C, C++
1212
- **智能切片 / Smart Slicing** — 项目概览 (~500 tokens) + 按模块切片 (~2-5k tokens),替代全量源码 (~200k+) / Project overview (~500 tokens) + per-module slices (~2-5k tokens) instead of full source (~200k+)
1313
- **变量追踪 / Variable Tracking** — 追踪模块级 const/static/let/var 声明,支持按 `--type variable` 查询 / Tracks module-level const/static/let/var declarations, queryable with `--type variable`
14-
- **行号级引用 / Line-Level References** — 跨文件引用精确到 import 行号 + 使用行号,而非仅文件名 / Cross-file references pinpoint import line + usage lines, not just file names
14+
- **行号级引用 / Line-Level References** — 跨文件引用精确到 import 行号 + 使用行号,同文件导出符号也追踪使用位置 / Cross-file references pinpoint import line + usage lines; same-file exported symbols also track usage locations
1515
- **增量更新 / Incremental Updates** — 基于文件哈希比较检测变更,仅重新解析修改的文件 / File hash comparison detects changes; only re-parses modified files
1616
- **影响分析 / Impact Analysis** — 重构前查看哪些模块会受影响 / See what's affected before you refactor
1717
- **自动触发 / Auto-Triggering** — Skill 根据对话上下文自动激活 / Skills activate automatically based on your conversation context
@@ -172,8 +172,8 @@ cd rust-cli && cargo test
172172
# 2. 提交并打 tag,CI 自动构建并发布 / Commit, tag, and let CI build & release
173173
cd ..
174174
git add .
175-
git commit -m "release: v0.2.5"
176-
git tag v0.2.5
175+
git commit -m "release: v0.2.6"
176+
git tag v0.2.6
177177
git push origin main --tags
178178
# GitHub Actions 会自动为所有平台构建并创建 Release
179179
# GitHub Actions will automatically build for all platforms and create a Release
@@ -312,7 +312,7 @@ The `codemap` skill auto-activates based on conversation context and intelligent
312312
| JavaScript | `.js`, `.jsx`, `.mjs`, `.cjs` | 函数、导入、导出、类、变量(const/let)/ Functions, imports, exports, classes, variables (const/let) |
313313
| Python | `.py` | 函数(含装饰器)、导入、`__all__` 导出、类、模块级变量 / Functions (decorated), imports, `__all__` exports, classes, module-level variables |
314314
| Go | `.go` | 函数、方法(含接收者)、导入、导出名、结构体、类型声明、变量(var/const)/ Functions, methods (with receiver), imports, exported names, structs, type specs, variables (var/const) |
315-
| Rust | `.rs` | 函数、impl 方法、use 声明、pub 导出、结构体、枚举、trait、变量(const/static)/ Functions, impl methods, use declarations, pub exports, structs, enums, traits, variables (const/static) |
315+
| Rust | `.rs` | 函数、impl 方法、use 声明、pub 导出(含 const/static)、结构体、枚举、trait、变量(const/static)/ Functions, impl methods, use declarations, pub exports (incl. const/static), structs, enums, traits, variables (const/static) |
316316
| Java | `.java` | 方法、构造器、导入、public 导出、类、接口、枚举、静态字段 / Methods, constructors, imports, public exports, classes, interfaces, enums, static fields |
317317
| C | `.c`, `.h` | 函数、`#include`、非 static 导出、结构体、枚举、typedef、全局变量 / Functions, `#include`, non-static exports, structs, enums, typedefs, global variables |
318318
| C++ | `.cpp`, `.cc`, `.cxx`, `.hpp`, `.hh` | 限定函数名(`Class::method`)、include、类、结构体、命名空间、全局变量 / Qualified functions (`Class::method`), includes, classes, structs, namespaces, global variables |
@@ -343,7 +343,7 @@ Scanning produces a `.codemap/` directory inside the target project:
343343
```bash
344344
cd rust-cli
345345
cargo test
346-
# 127 tests, all passing
346+
# 95 unit tests, all passing
347347
```
348348

349349
## 许可证 / License

ccplugin/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codemap",
3-
"version": "0.2.5",
3+
"version": "0.2.6",
44
"description": "AST-based code graph mapping plugin for Claude Code — scan once, persist structural graph, load compact slices to save ~95% tokens",
55
"author": {
66
"name": "killvxk",

rust-cli/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust-cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "codegraph"
3-
version = "0.2.5"
3+
version = "0.2.6"
44
edition = "2021"
55

66
[[bin]]

rust-cli/src/commands/slice.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ pub fn run(args: SliceArgs) {
6767
} else {
6868
match graph.modules.get(&mod_name) {
6969
Some(mod_data) => {
70-
let slice =
71-
crate::slicer::build_module_slice(&graph, &mod_name, mod_data);
70+
let slice = crate::slicer::build_module_slice(&graph, &mod_name, mod_data);
7271
match serde_json::to_string_pretty(&slice) {
7372
Ok(json) => println!("{}", json),
7473
Err(e) => {

rust-cli/src/commands/status.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ pub fn run(args: StatusArgs) {
3737

3838
println!("Project: {}", graph.project.name);
3939
println!("Scanned at: {}", graph.scanned_at);
40-
println!("Commit: {}", graph.commit_hash.as_deref().unwrap_or("(none)"));
40+
println!(
41+
"Commit: {}",
42+
graph.commit_hash.as_deref().unwrap_or("(none)")
43+
);
4144
println!("Files: {}", graph.summary.total_files);
4245
println!("Functions: {}", graph.summary.total_functions);
4346
println!("Classes: {}", graph.summary.total_classes);
@@ -60,9 +63,6 @@ pub fn run(args: StatusArgs) {
6063
}
6164

6265
// 已追踪文件数
63-
let tracked = meta
64-
.as_ref()
65-
.map(|m| m.file_hashes.len())
66-
.unwrap_or(0);
66+
let tracked = meta.as_ref().map(|m| m.file_hashes.len()).unwrap_or(0);
6767
println!("Tracked files: {tracked}");
6868
}

rust-cli/src/commands/update.rs

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,14 @@ pub fn run(args: UpdateArgs) {
6767

6868
// 从 meta.fileHashes 读取旧哈希(与 Node.js update 逻辑一致)
6969
// 若 meta.json 不存在或无 fileHashes,回退到从 graph.files 提取
70-
let old_hashes: HashMap<String, String> =
71-
match crate::graph::load_meta(&codemap_dir) {
72-
Ok(meta) if !meta.file_hashes.is_empty() => meta.file_hashes.into_iter().collect(),
73-
_ => graph
74-
.files
75-
.iter()
76-
.map(|(p, f)| (p.clone(), f.hash.clone()))
77-
.collect(),
78-
};
70+
let old_hashes: HashMap<String, String> = match crate::graph::load_meta(&codemap_dir) {
71+
Ok(meta) if !meta.file_hashes.is_empty() => meta.file_hashes.into_iter().collect(),
72+
_ => graph
73+
.files
74+
.iter()
75+
.map(|(p, f)| (p.clone(), f.hash.clone()))
76+
.collect(),
77+
};
7978

8079
// 检测变更
8180
let changes = crate::differ::detect_changed_files(&old_hashes, &new_hashes);
@@ -113,7 +112,10 @@ pub fn run(args: UpdateArgs) {
113112

114113
let mut ts_parser = tree_sitter::Parser::new();
115114
if ts_parser.set_language(&adapter.language()).is_err() {
116-
eprintln!("Warning: failed to set language for {:?}, skipping", abs_path);
115+
eprintln!(
116+
"Warning: failed to set language for {:?}, skipping",
117+
abs_path
118+
);
117119
continue;
118120
}
119121
let tree = match ts_parser.parse(content, None) {
@@ -136,19 +138,24 @@ pub fn run(args: UpdateArgs) {
136138
let variables = crate::scanner::convert_variables(&lang_variables);
137139

138140
// 构建 symbol_refs(与 scan_project 一致)
139-
let imported_symbols: std::collections::HashSet<String> = imports.iter()
141+
let imported_symbols: std::collections::HashSet<String> = imports
142+
.iter()
140143
.flat_map(|imp| imp.symbols.iter().cloned())
141144
.collect();
142145
let symbol_uses = crate::scanner::scan_symbol_uses(&tree, content, &imported_symbols);
143-
let mut symbol_refs: std::collections::BTreeMap<String, crate::graph::SymbolRef> = std::collections::BTreeMap::new();
146+
let mut symbol_refs: std::collections::BTreeMap<String, crate::graph::SymbolRef> =
147+
std::collections::BTreeMap::new();
144148
for imp in &imports {
145149
for sym in &imp.symbols {
146150
let use_lines = symbol_uses.get(sym).cloned().unwrap_or_default();
147-
symbol_refs.insert(sym.clone(), crate::graph::SymbolRef {
148-
symbol: sym.clone(),
149-
import_line: imp.import_line,
150-
use_lines,
151-
});
151+
symbol_refs.insert(
152+
sym.clone(),
153+
crate::graph::SymbolRef {
154+
symbol: sym.clone(),
155+
import_line: imp.import_line,
156+
use_lines,
157+
},
158+
);
152159
}
153160
}
154161

rust-cli/src/differ.rs

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,12 @@ pub fn detect_changed_files(
5555
removed.sort();
5656
unchanged.sort();
5757

58-
ChangeSet { added, modified, removed, unchanged }
58+
ChangeSet {
59+
added,
60+
modified,
61+
removed,
62+
unchanged,
63+
}
5964
}
6065

6166
/// 将变更合并到现有图谱(原地修改)
@@ -91,11 +96,14 @@ pub fn merge_graph_update(
9196
}
9297

9398
// 确保目标模块存在
94-
graph.modules.entry(file_data.module.clone()).or_insert_with(|| ModuleEntry {
95-
files: vec![],
96-
depends_on: vec![],
97-
depended_by: vec![],
98-
});
99+
graph
100+
.modules
101+
.entry(file_data.module.clone())
102+
.or_insert_with(|| ModuleEntry {
103+
files: vec![],
104+
depends_on: vec![],
105+
depended_by: vec![],
106+
});
99107

100108
// 将文件加入模块(避免重复,用 HashSet 检查)
101109
let mod_name = file_data.module.clone();
@@ -167,7 +175,9 @@ fn rebuild_dependencies(graph: &mut CodeGraph) {
167175
path_lookup.insert(norm.clone(), file_data.module.clone());
168176
// 无扩展名版本
169177
let without_ext = strip_extension(&norm);
170-
path_lookup.entry(without_ext).or_insert_with(|| file_data.module.clone());
178+
path_lookup
179+
.entry(without_ext)
180+
.or_insert_with(|| file_data.module.clone());
171181
}
172182

173183
// 用 Set 收集依赖关系
@@ -211,13 +221,17 @@ fn rebuild_dependencies(graph: &mut CodeGraph) {
211221

212222
// 写回图谱
213223
for (mod_name, module) in &mut graph.modules {
214-
let mut dep_on: Vec<String> =
215-
depends_on.get(mod_name).map(|s| s.iter().cloned().collect()).unwrap_or_default();
224+
let mut dep_on: Vec<String> = depends_on
225+
.get(mod_name)
226+
.map(|s| s.iter().cloned().collect())
227+
.unwrap_or_default();
216228
dep_on.sort();
217229
module.depends_on = dep_on;
218230

219-
let mut dep_by: Vec<String> =
220-
depended_by.get(mod_name).map(|s| s.iter().cloned().collect()).unwrap_or_default();
231+
let mut dep_by: Vec<String> = depended_by
232+
.get(mod_name)
233+
.map(|s| s.iter().cloned().collect())
234+
.unwrap_or_default();
221235
dep_by.sort();
222236
module.depended_by = dep_by;
223237
}
@@ -320,7 +334,9 @@ mod tests {
320334
#[test]
321335
fn test_merge_remove_file() {
322336
let mut graph = create_empty_graph("test", "/tmp/test");
323-
graph.files.insert("src/a.ts".to_string(), make_file_entry("auth"));
337+
graph
338+
.files
339+
.insert("src/a.ts".to_string(), make_file_entry("auth"));
324340
graph.modules.insert(
325341
"auth".to_string(),
326342
ModuleEntry {
@@ -357,7 +373,9 @@ mod tests {
357373
#[test]
358374
fn test_merge_module_change() {
359375
let mut graph = create_empty_graph("test", "/tmp/test");
360-
graph.files.insert("src/a.ts".to_string(), make_file_entry("old_mod"));
376+
graph
377+
.files
378+
.insert("src/a.ts".to_string(), make_file_entry("old_mod"));
361379
graph.modules.insert(
362380
"old_mod".to_string(),
363381
ModuleEntry {
@@ -391,10 +409,14 @@ mod tests {
391409
is_external: false,
392410
import_line: 0,
393411
}];
394-
graph.files.insert("src/auth/login.ts".to_string(), auth_file);
412+
graph
413+
.files
414+
.insert("src/auth/login.ts".to_string(), auth_file);
395415

396416
let utils_file = make_file_entry("utils");
397-
graph.files.insert("src/utils/helper.ts".to_string(), utils_file);
417+
graph
418+
.files
419+
.insert("src/utils/helper.ts".to_string(), utils_file);
398420

399421
graph.modules.insert(
400422
"auth".to_string(),

rust-cli/src/grammar_tests.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ mod grammar_tests {
9898
fn test_parse_simple_typescript() {
9999
let lang = lang_typescript();
100100
let mut parser = tree_sitter::Parser::new();
101-
parser.set_language(&lang).expect("Failed to set TypeScript language");
101+
parser
102+
.set_language(&lang)
103+
.expect("Failed to set TypeScript language");
102104
let tree = parser.parse("const x: number = 42;", None);
103105
assert!(tree.is_some());
104106
}
@@ -107,7 +109,9 @@ mod grammar_tests {
107109
fn test_parse_simple_python() {
108110
let lang = lang_python();
109111
let mut parser = tree_sitter::Parser::new();
110-
parser.set_language(&lang).expect("Failed to set Python language");
112+
parser
113+
.set_language(&lang)
114+
.expect("Failed to set Python language");
111115
let tree = parser.parse("def hello(): pass", None);
112116
assert!(tree.is_some());
113117
}

rust-cli/src/graph.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,7 @@ pub struct MetaInfo {
155155

156156
// ── 入口点文件名集合 ──────────────────────────────────────────────────────────
157157

158-
const ENTRY_POINT_NAMES: &[&str] = &[
159-
"main", "index", "server", "app", "entry", "bootstrap",
160-
];
158+
const ENTRY_POINT_NAMES: &[&str] = &["main", "index", "server", "app", "entry", "bootstrap"];
161159

162160
// ── 公共函数 ──────────────────────────────────────────────────────────────────
163161

@@ -261,7 +259,10 @@ pub fn chrono_now() -> String {
261259
.as_secs();
262260
// 简单的 UTC 时间格式化
263261
let (y, mo, d, h, mi, s) = unix_to_datetime(secs);
264-
format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.000Z", y, mo, d, h, mi, s)
262+
format!(
263+
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.000Z",
264+
y, mo, d, h, mi, s
265+
)
265266
}
266267

267268
fn unix_to_datetime(secs: u64) -> (u64, u64, u64, u64, u64, u64) {
@@ -284,7 +285,20 @@ fn unix_to_datetime(secs: u64) -> (u64, u64, u64, u64, u64, u64) {
284285
days -= days_in_year;
285286
year += 1;
286287
}
287-
let months = [31u64, if is_leap(year) { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
288+
let months = [
289+
31u64,
290+
if is_leap(year) { 29 } else { 28 },
291+
31,
292+
30,
293+
31,
294+
30,
295+
31,
296+
31,
297+
30,
298+
31,
299+
30,
300+
31,
301+
];
288302
let mut month = 1u64;
289303
for &m in &months {
290304
if days < m {

0 commit comments

Comments
 (0)