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
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,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// Initialize the code graph database and scan the project
Init,
/// Sync the code graph database with incremental updates for modified files
Sync,
/// 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,
},
/// 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>,
},
/// Explore the codebase using FTS and show relationships
Explore {
/// Query terms to search for nodes
query: String,
},
}
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;
match cli.command {
Commands::Init => {
commands::run_init(¤t_dir)?;
}
Commands::Sync => {
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)?;
}
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)?;
}
Commands::Callers { symbol } => {
commands::run_callers(¤t_dir, &symbol, json)?;
}
Commands::Node {
file,
offset,
limit,
symbols_only,
symbol,
include_code,
line,
} => {
commands::run_node(
¤t_dir,
file,
offset,
limit,
symbols_only,
symbol,
include_code,
line,
json,
)?;
}
Commands::Explore { query } => {
commands::run_explore(¤t_dir, &query, json)?;
}
}
Ok(())
}