devscout_rs/cli/
dispatch.rs1use std::io::{Read, Write};
12use std::process;
13
14use crate::audit;
15use crate::extract;
16use crate::hookio;
17use crate::initcmd;
18use crate::parse;
19
20use super::admin::{cmd_clear, cmd_map, cmd_stats};
21use super::answer::{
22 emit_freshness_warning, emit_zero_hit_note, ZERO_HIT_FIND, ZERO_HIT_IMPACT, ZERO_HIT_TESTS,
23};
24use super::args::first_positional;
25use super::compiler_facts::cmd_compiler_facts;
26use super::coverage::cmd_tests;
27use super::find::cmd_find;
28use super::impact::cmd_impact;
29use super::import_edges::cmd_import_edges;
30use super::read::cmd_read;
31use super::refs::cmd_refs;
32use super::root::{apply_global_options, current_dir};
33
34fn print_out(out: &str) {
41 use std::io::Write;
42 let stdout = std::io::stdout();
43 let mut lock = stdout.lock();
44 let _ = lock
45 .write_all(out.as_bytes())
46 .and_then(|()| lock.write_all(b"\n"));
47}
48
49const HELP: &str = "\
53devscout -- fast code index for C# and TypeScript codebases.
54
55usage: devscout <command> [args]
56
57index
58 init [scope ...] register this repo, install hooks, first map
59 [--label L] [--no-hooks] [--no-map]
60 map [scope ...] [--refresh] [--no-semantic] build the index for the given scopes
61 clear --older-than <days> drop freshness rows older than N days
62 clear --session <id> drop one session's freshness rows
63
64query
65 find <query> [--resources] search the manifest by name or purpose
66 refs <symbol> references to a symbol [--out --all --no-guess --no-dispatch --no-bus --pick N --json|--compact]
67 read <symbol> decl span + inbound refs [--no-guess --no-dispatch --no-bus --pick N --json|--compact]
68 impact <file|symbol> blast radius [--hops N --no-guess --no-dispatch --no-bus --no-imports --pick N --json|--compact]
69 tests <symbol> tests reaching a symbol [--no-guess --no-dispatch --no-bus --pick N --json|--compact]
70 import-edges <file> --repo <id> load a cross-repo edge export for `impact` to read
71 compiler-facts run|import|status optional engine-derived facts [--target --configuration --platform]
72 stats index + cache summary for this repo
73
74plumbing
75 parse <file.cs> dump the parse tree
76 spans <file.cs> dump declaration spans
77 extract-dump <file.cs> dump extraction records
78 audit --semantic <refs.jsonl> score uses-member edges against a semantic oracle [--units F] [--defs F] [--json] [--assert F] [--fp-sites F]
79 hook <read|bash> agent hook filters, stdin -> stdout
80 noop exit 0 (harness probe)
81
82 -C <dir> run as if devscout started in <dir>
83 -h, --help show this help
84 -V, --version show the version
85";
86
87#[allow(
89 clippy::too_many_lines,
90 reason = "one flat match over every subcommand; splitting it would scatter the dispatch table across files"
91)]
92pub fn dispatch(args: Vec<String>) {
93 let (cwd, args) = match apply_global_options(¤t_dir(), &args) {
94 Ok(v) => v,
95 Err(e) => {
96 print_out(&format!("error: {e}"));
97 process::exit(1);
98 }
99 };
100 match args.get(1).map(String::as_str) {
101 Some("noop") => process::exit(0),
102 Some("help") | Some("--help") | Some("-h") => {
103 print!("{HELP}");
104 process::exit(0);
105 }
106 Some("--version") | Some("-V") => {
107 println!("devscout {}", env!("CARGO_PKG_VERSION"));
108 process::exit(0);
109 }
110 Some("parse") => {
111 let Some(path) = args.get(2) else {
112 eprintln!("usage: devscout parse <file.cs>");
113 process::exit(1);
114 };
115 parse::run_parse(path);
116 }
117 Some("spans") => {
118 let Some(path) = args.get(2) else {
119 eprintln!("usage: devscout spans <file.cs>");
120 process::exit(1);
121 };
122 parse::run_spans(path);
123 }
124 Some("extract-dump") => {
125 let Some(path) = args.get(2) else {
126 eprintln!("usage: devscout extract-dump <file.cs>");
127 process::exit(1);
128 };
129 extract::run_extract_dump(path);
130 }
131 Some("audit") => {
132 let (code, out) = audit::cmd_audit(&cwd, &args[2..]);
133 print_out(&out);
134 process::exit(code);
135 }
136 Some("hook") => match args.get(2).map(String::as_str) {
137 Some("read") => run_hook(hookio::run_read),
138 Some("bash") => run_hook(hookio::run_bash),
139 _ => {
140 eprintln!("usage: devscout hook <read|bash>");
141 process::exit(1);
142 }
143 },
144 Some("refs") => {
145 let (code, out, note) = cmd_refs(&cwd, &args[2..]);
146 print_out(&out);
147 emit_freshness_warning(&cwd);
148 emit_zero_hit_note(code, note, &cwd, first_positional(&args[2..]));
149 process::exit(code);
150 }
151 Some("read") => {
152 let (code, out, note) = cmd_read(&cwd, &args[2..]);
153 print_out(&out);
154 emit_freshness_warning(&cwd);
155 emit_zero_hit_note(code, note, &cwd, first_positional(&args[2..]));
156 process::exit(code);
157 }
158 Some("impact") => {
159 let (code, out) = cmd_impact(&cwd, &args[2..]);
160 print_out(&out);
161 emit_freshness_warning(&cwd);
162 emit_zero_hit_note(code, Some(ZERO_HIT_IMPACT), &cwd, None);
163 process::exit(code);
164 }
165 Some("tests") => {
166 let (code, out) = cmd_tests(&cwd, &args[2..]);
167 print_out(&out);
168 emit_freshness_warning(&cwd);
169 emit_zero_hit_note(code, Some(ZERO_HIT_TESTS), &cwd, None);
170 process::exit(code);
171 }
172 Some("find") => {
173 let resources = args[2..].iter().any(|a| a == "--resources");
177 let query_str = args[2..]
178 .iter()
179 .filter(|a| a.as_str() != "--resources")
180 .cloned()
181 .collect::<Vec<_>>()
182 .join(" ");
183 let (code, out) = cmd_find(&cwd, &query_str, resources);
184 print_out(&out);
185 emit_freshness_warning(&cwd);
186 emit_zero_hit_note(code, Some(ZERO_HIT_FIND), &cwd, Some(query_str.as_str()));
187 process::exit(code);
188 }
189 Some("import-edges") => {
190 let (code, out) = cmd_import_edges(&cwd, &args[2..]);
191 print_out(&out);
192 process::exit(code);
193 }
194 Some("compiler-facts") => {
195 let (code, out) = cmd_compiler_facts(&cwd, &args[2..]);
196 print_out(&out);
197 process::exit(code);
198 }
199 Some("map") => {
200 let (code, out) = cmd_map(&cwd, &args[2..]);
201 print_out(&out);
202 process::exit(code);
203 }
204 Some("stats") => {
205 let (code, out) = cmd_stats(&cwd);
206 print_out(&out);
207 process::exit(code);
208 }
209 Some("clear") => {
210 let (code, out) = cmd_clear(&cwd, &args[2..]);
211 print_out(&out);
212 process::exit(code);
213 }
214 Some("init") => {
215 let (code, out) = initcmd::cmd_init_full(&cwd, &args[2..]);
216 print_out(&out);
217 process::exit(code);
218 }
219 _ => {
220 eprintln!("usage: devscout <noop|parse|spans|extract-dump|hook|refs|read|impact|tests|find|import-edges|compiler-facts|map|stats|clear|init> [args]");
221 process::exit(1);
222 }
223 }
224}
225
226fn run_hook(handler: fn(&[u8]) -> Vec<u8>) {
233 let mut buf = Vec::new();
234 let _ = std::io::stdin().read_to_end(&mut buf);
235 let out = handler(&buf);
236 if !out.is_empty() {
237 let _ = std::io::stdout().write_all(&out);
238 }
239 process::exit(0);
240}