1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
pub mod commands;
pub mod db;
pub mod parser;
use clap::{Parser, Subcommand};
use std::error::Error;
#[derive(Parser, Debug)]
#[command(name = "ochna")]
#[command(author, version, about = "Code graph indexing and analysis tool", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
/// Emit machine-readable JSON results on stdout instead of human text
#[arg(long, global = true)]
json: bool,
/// Exclude symbols classified as test code from query results
#[arg(long = "no-tests", global = true)]
no_tests: bool,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// Initialize the code graph database and scan the project
Init {
/// Include vendored/build/library directories such as target, node_modules, .venv, vendor, build, and dist
#[arg(long = "include-library")]
include_library: bool,
},
/// Sync the code graph database with incremental updates for modified files
Sync {
/// Include vendored/build/library directories such as target, node_modules, .venv, vendor, build, and dist
#[arg(long = "include-library")]
include_library: bool,
},
/// Display index statistics
Status,
/// List indexed files with metadata
Files,
/// Search for nodes/symbols matching a query string
Search {
/// The search query
query: String,
},
/// Find callers of a given symbol
Callers {
/// The name or ID of the symbol to query
symbol: String,
/// Minimum confidence level to include in callers results (e.g. 80)
#[arg(long = "min-confidence")]
min_confidence: Option<i64>,
/// Display resolution kind and confidence metrics alongside symbols
#[arg(long = "show-resolution")]
show_resolution: bool,
},
/// Inspect details of a file or a symbol
Node {
/// The relative path of the file to inspect
#[arg(long)]
file: Option<String>,
/// 1-based start line number for file mode
#[arg(long)]
offset: Option<i64>,
/// Number of lines to read in file mode
#[arg(long)]
limit: Option<i64>,
/// If true, only list the symbols in the file
#[arg(long = "symbols-only")]
symbols_only: bool,
/// The name or ID of the symbol to query
#[arg(long)]
symbol: Option<String>,
/// If true, include the source code of the symbol
#[arg(long = "include-code")]
include_code: bool,
/// Specific line number to filter by (symbol mode only)
#[arg(long)]
line: Option<i64>,
/// Display resolution kind and confidence metrics alongside symbols
#[arg(long = "show-resolution")]
show_resolution: bool,
},
/// Explore the codebase using FTS and show relationships
Explore {
/// Query terms to search for nodes
query: String,
/// Display resolution kind and confidence metrics alongside symbols
#[arg(long = "show-resolution")]
show_resolution: bool,
},
}
fn main() -> Result<(), Box<dyn Error>> {
let cli = Cli::parse();
// Diagnostics (progress, warnings, errors) go to stderr via tracing so that
// stdout carries only command results — keeping `--json` output clean for agents.
// Verbosity is controlled by RUST_LOG (defaults to `info`).
tracing_subscriber::fmt()
.with_writer(std::io::stderr)
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
)
.with_target(false)
.without_time()
.init();
let current_dir = std::env::current_dir()?;
let json = cli.json;
let no_tests = cli.no_tests;
match cli.command {
Commands::Init { include_library } => {
commands::run_init(¤t_dir, include_library)?;
}
Commands::Sync { include_library } => {
let ochna_dir = current_dir.join(".ochna");
if !ochna_dir.exists() {
return Err("Database not initialized. Please run 'ochna init' first.".into());
}
commands::run_init(¤t_dir, include_library)?;
}
Commands::Status => {
commands::run_status(¤t_dir, json)?;
}
Commands::Files => {
commands::run_files(¤t_dir, json)?;
}
Commands::Search { query } => {
commands::run_search(¤t_dir, &query, json, no_tests)?;
}
Commands::Callers {
symbol,
min_confidence,
show_resolution,
} => {
commands::run_callers(
¤t_dir,
&symbol,
json,
no_tests,
min_confidence,
show_resolution,
)?;
}
Commands::Node {
file,
offset,
limit,
symbols_only,
symbol,
include_code,
line,
show_resolution,
} => {
commands::run_node(
¤t_dir,
file,
offset,
limit,
symbols_only,
symbol,
include_code,
line,
json,
no_tests,
show_resolution,
)?;
}
Commands::Explore {
query,
show_resolution,
} => {
commands::run_explore(¤t_dir, &query, json, no_tests, show_resolution)?;
}
}
Ok(())
}