1use std::fmt::{self, Display};
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(rename_all = "snake_case")]
7pub enum Language {
8 JavaScript,
9 TypeScript,
10 Tsx,
13 Python,
14 Go,
15 Rust,
16 Java,
17 C,
18 Cpp,
21 CSharp,
22 Ruby,
23 Php,
24}
25
26impl Language {
27 pub fn from_extension(ext: &str) -> Option<Self> {
28 match ext {
29 "js" | "jsx" | "mjs" | "cjs" => Some(Self::JavaScript),
30 "ts" | "mts" | "cts" => Some(Self::TypeScript),
31 "tsx" => Some(Self::Tsx),
32 "py" | "pyw" => Some(Self::Python),
33 "go" => Some(Self::Go),
34 "rs" => Some(Self::Rust),
35 "java" => Some(Self::Java),
36 "c" => Some(Self::C),
37 "cpp" | "cc" | "cxx" | "hpp" | "hh" | "hxx" | "h" => Some(Self::Cpp),
40 "cs" => Some(Self::CSharp),
41 "rb" => Some(Self::Ruby),
42 "php" | "phtml" => Some(Self::Php),
43 _ => None,
44 }
45 }
46
47 pub fn from_name(name: &str) -> Option<Self> {
48 match name.to_lowercase().as_str() {
49 "javascript" | "js" | "jsx" => Some(Self::JavaScript),
50 "typescript" | "ts" => Some(Self::TypeScript),
51 "tsx" => Some(Self::Tsx),
52 "python" | "py" => Some(Self::Python),
53 "go" => Some(Self::Go),
54 "rust" | "rs" => Some(Self::Rust),
55 "java" => Some(Self::Java),
56 "c" => Some(Self::C),
57 "cpp" | "c++" | "cxx" | "cc" => Some(Self::Cpp),
58 "csharp" | "c#" | "cs" => Some(Self::CSharp),
59 "ruby" | "rb" => Some(Self::Ruby),
60 "php" => Some(Self::Php),
61 _ => None,
62 }
63 }
64
65 pub fn family(&self) -> &'static str {
68 match self {
69 Self::JavaScript => "javascript",
70 Self::TypeScript | Self::Tsx => "typescript",
71 Self::Python => "python",
72 Self::Go => "go",
73 Self::Rust => "rust",
74 Self::Java => "java",
75 Self::C => "c",
76 Self::Cpp => "cpp",
77 Self::CSharp => "csharp",
78 Self::Ruby => "ruby",
79 Self::Php => "php",
80 }
81 }
82}
83
84impl Display for Language {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 f.write_str(self.family())
87 }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
91#[serde(rename_all = "snake_case")]
92pub enum GraphEngineKind {
93 Sqlite,
94 Cozo,
95}
96
97impl Display for GraphEngineKind {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 match self {
100 Self::Sqlite => f.write_str("sqlite"),
101 Self::Cozo => f.write_str("cozo"),
102 }
103 }
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct SymbolRecord {
108 pub id: i64,
109 pub name: String,
110 pub qualified_name: String,
111 pub kind: String,
112 pub file_id: i64,
113 pub path: String,
114 pub language: String,
115 pub start_line: usize,
116 pub end_line: usize,
117 pub signature: String,
118 pub exported: bool,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct ReferenceRecord {
123 pub id: i64,
124 pub symbol_name: String,
125 pub from_symbol_id: Option<i64>,
126 pub from_symbol: Option<String>,
127 pub path: String,
128 pub line: usize,
129 pub column: usize,
130 pub context: String,
131 pub kind: String,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct IndexedSymbol {
136 pub name: String,
137 pub qualified_name: String,
138 pub kind: String,
139 pub start_line: usize,
140 pub end_line: usize,
141 pub signature: String,
142 pub exported: bool,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct IndexedImport {
147 pub source: String,
148 pub line: usize,
149 pub kind: String,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct IndexedReference {
154 pub symbol_name: String,
155 pub from_qualified_name: Option<String>,
156 pub line: usize,
157 pub column: usize,
158 pub context: String,
159 pub kind: String,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct DefinitionResult {
164 pub matches: Vec<SymbolRecord>,
165 pub meta: QueryMeta,
166}
167
168impl Display for DefinitionResult {
169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170 if self.matches.is_empty() {
171 writeln!(f, "No definitions found.")?;
172 return writeln!(
173 f,
174 " hint: run `tessera search <name>` for fuzzy lookup, `tessera validate <name>` for near-misses, or re-index with `tessera index . --full` if the DB is stale."
175 );
176 }
177 for symbol in &self.matches {
178 writeln!(
179 f,
180 "{} {} at {}:{}",
181 symbol.kind, symbol.qualified_name, symbol.path, symbol.start_line
182 )?;
183 if !symbol.signature.is_empty() {
184 writeln!(f, " {}", symbol.signature)?;
185 }
186 }
187 write_meta(f, &self.meta)
188 }
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct ReferencesResult {
193 pub references: Vec<ReferenceRecord>,
194 pub meta: QueryMeta,
195}
196
197impl Display for ReferencesResult {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 if self.references.is_empty() {
200 writeln!(f, "No references found.")?;
201 return writeln!(
202 f,
203 " hint: run `tessera impact <symbol>` for transitive callers, or `tessera search <symbol>` to confirm the indexed name."
204 );
205 }
206 for reference in &self.references {
207 writeln!(
208 f,
209 "{}:{}:{} {}",
210 reference.path, reference.line, reference.column, reference.context
211 )?;
212 }
213 write_meta(f, &self.meta)
214 }
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct OutlineResult {
219 pub path: String,
220 pub symbols: Vec<SymbolRecord>,
221 pub meta: QueryMeta,
222}
223
224impl Display for OutlineResult {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 if self.symbols.is_empty() {
227 writeln!(f, "No indexed symbols under {}.", self.path)?;
228 writeln!(
229 f,
230 " hint: check the path prefix, run `tessera stats`, or re-index with `tessera index . --full`."
231 )?;
232 } else {
233 writeln!(f, "Outline for {}", self.path)?;
234 for symbol in &self.symbols {
235 writeln!(
236 f,
237 " {:<9} {:<36} {}:{}",
238 symbol.kind, symbol.qualified_name, symbol.path, symbol.start_line
239 )?;
240 }
241 }
242 write_meta(f, &self.meta)
243 }
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct ExpandResult {
248 pub symbol: Option<SymbolRecord>,
249 pub body: Option<String>,
250 pub dependencies: Vec<ReferenceRecord>,
251 pub meta: QueryMeta,
252}
253
254impl Display for ExpandResult {
255 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256 let Some(symbol) = &self.symbol else {
257 writeln!(f, "No symbol found.")?;
258 return writeln!(
259 f,
260 " hint: run `tessera find-definition <symbol>` or `tessera search <symbol>` to find the indexed name."
261 );
262 };
263 writeln!(
264 f,
265 "{} {} at {}:{}-{}",
266 symbol.kind, symbol.qualified_name, symbol.path, symbol.start_line, symbol.end_line
267 )?;
268 if let Some(body) = &self.body {
269 writeln!(f, "\n{body}")?;
270 }
271 if !self.dependencies.is_empty() {
272 writeln!(f, "\nImmediate dependencies:")?;
273 for dep in &self.dependencies {
274 writeln!(f, " {} at line {}", dep.symbol_name, dep.line)?;
275 }
276 }
277 write_meta(f, &self.meta)
278 }
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct ImpactResult {
283 pub symbol: String,
284 pub callers: Vec<ImpactCaller>,
285 pub meta: QueryMeta,
286}
287
288impl Display for ImpactResult {
289 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290 if self.callers.is_empty() {
291 writeln!(f, "No callers found for {}.", self.symbol)?;
292 writeln!(
293 f,
294 " hint: run `tessera find-references {}` for direct refs, `tessera validate {}` for near-misses, or re-index if the call graph is stale.",
295 self.symbol, self.symbol
296 )?;
297 } else {
298 writeln!(f, "Impact for {}", self.symbol)?;
299 for caller in &self.callers {
300 writeln!(
301 f,
302 " score {:>5.1} depth {} fanout {:>3} {} at {}:{}",
303 caller.criticality,
304 caller.depth,
305 caller.breakdown.fanout_in,
306 caller.symbol.qualified_name,
307 caller.symbol.path,
308 caller.symbol.start_line
309 )?;
310 }
311 }
312 write_meta(f, &self.meta)
313 }
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
317pub struct ImpactCaller {
318 pub symbol: SymbolRecord,
319 pub depth: usize,
320 pub criticality: f32,
321 pub breakdown: CriticalityBreakdown,
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct CriticalityBreakdown {
326 pub pagerank: f32,
327 pub fanout_in: usize,
328 pub fanout_out: usize,
329 pub exported: bool,
330 pub test_coverage: usize,
331 pub depth_decay: f32,
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct QueryMeta {
336 pub tokens_returned_estimate: usize,
337 pub alternative_queries: Vec<AlternativeQuery>,
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize)]
341pub struct AlternativeQuery {
342 pub tool: String,
343 pub tokens_estimate: usize,
344 pub fidelity: f32,
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize)]
348pub struct SymbolSuggestion {
349 pub qualified_name: String,
350 pub name: String,
351 pub path: String,
352 pub line: usize,
353 pub confidence: f32,
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct ValidateResult {
358 pub query: String,
359 pub exists: bool,
360 pub bloom_hit: bool,
361 pub candidates: Vec<SymbolSuggestion>,
362 pub meta: QueryMeta,
363}
364
365impl Display for ValidateResult {
366 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367 if self.exists {
368 writeln!(f, "✓ {} exists in the graph.", self.query)?;
369 } else {
370 writeln!(f, "✗ {} not found (bloom={}).", self.query, self.bloom_hit)?;
371 }
372 if !self.candidates.is_empty() {
373 writeln!(f, "Nearest candidates:")?;
374 for candidate in &self.candidates {
375 writeln!(
376 f,
377 " {:>4.2} {} at {}:{}",
378 candidate.confidence, candidate.qualified_name, candidate.path, candidate.line
379 )?;
380 }
381 } else if !self.exists {
382 writeln!(
383 f,
384 " hint: run `tessera search {}` for broader fuzzy matching, or re-index with `tessera index . --full` if this symbol should exist.",
385 self.query
386 )?;
387 }
388 write_meta(f, &self.meta)
389 }
390}
391
392#[derive(Debug, Clone, Serialize, Deserialize)]
393pub struct SnippetReferenceCheck {
394 pub symbol_name: String,
395 pub line: usize,
396 pub column: usize,
397 pub exists: bool,
398 pub candidates: Vec<SymbolSuggestion>,
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize)]
402pub struct ValidateSnippetResult {
403 pub language: String,
404 pub total_calls: usize,
405 pub unresolved_calls: usize,
406 pub checks: Vec<SnippetReferenceCheck>,
407 pub meta: QueryMeta,
408}
409
410impl Display for ValidateSnippetResult {
411 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412 writeln!(
413 f,
414 "Validated {} calls ({} unresolved) in {} snippet.",
415 self.total_calls, self.unresolved_calls, self.language
416 )?;
417 for check in &self.checks {
418 let mark = if check.exists { "✓" } else { "✗" };
419 writeln!(
420 f,
421 " {} {} at line {} col {}",
422 mark, check.symbol_name, check.line, check.column
423 )?;
424 if !check.exists {
425 for candidate in check.candidates.iter().take(3) {
426 writeln!(
427 f,
428 " -> maybe {} ({:.2})",
429 candidate.qualified_name, candidate.confidence
430 )?;
431 }
432 }
433 }
434 write_meta(f, &self.meta)
435 }
436}
437
438#[derive(Debug, Clone, Serialize, Deserialize)]
439pub struct ContextPack {
440 pub symbol: Option<SymbolRecord>,
441 pub body: Option<String>,
442 pub dependency_signatures: Vec<SignatureLine>,
443 pub caller_signatures: Vec<SignatureLine>,
444 pub tests: Vec<SymbolRecord>,
445 pub budget_tokens: usize,
446 pub meta: QueryMeta,
447}
448
449#[derive(Debug, Clone, Serialize, Deserialize)]
450pub struct SignatureLine {
451 pub qualified_name: String,
452 pub kind: String,
453 pub path: String,
454 pub line: usize,
455 pub signature: String,
456}
457
458impl Display for ContextPack {
459 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460 let Some(symbol) = &self.symbol else {
461 return writeln!(f, "No symbol found.");
462 };
463 writeln!(
464 f,
465 "{} {} at {}:{} (budget ~{} tokens)",
466 symbol.kind, symbol.qualified_name, symbol.path, symbol.start_line, self.budget_tokens
467 )?;
468 if let Some(body) = &self.body {
469 writeln!(f, "\n# body\n{body}")?;
470 }
471 if !self.dependency_signatures.is_empty() {
472 writeln!(f, "\n# dependencies")?;
473 for sig in &self.dependency_signatures {
474 writeln!(f, " {} @ {}:{}", sig.qualified_name, sig.path, sig.line)?;
475 if !sig.signature.is_empty() {
476 writeln!(f, " {}", sig.signature)?;
477 }
478 }
479 }
480 if !self.caller_signatures.is_empty() {
481 writeln!(f, "\n# callers")?;
482 for sig in &self.caller_signatures {
483 writeln!(f, " {} @ {}:{}", sig.qualified_name, sig.path, sig.line)?;
484 if !sig.signature.is_empty() {
485 writeln!(f, " {}", sig.signature)?;
486 }
487 }
488 }
489 if !self.tests.is_empty() {
490 writeln!(f, "\n# tests")?;
491 for test in &self.tests {
492 writeln!(
493 f,
494 " {} @ {}:{}",
495 test.qualified_name, test.path, test.start_line
496 )?;
497 }
498 }
499 write_meta(f, &self.meta)
500 }
501}
502
503#[derive(Debug, Clone, Serialize, Deserialize)]
504pub struct PlanQueryResult {
505 pub query: String,
506 pub inferred_intent: String,
507 pub steps: Vec<PlanStep>,
508 pub meta: QueryMeta,
509}
510
511#[derive(Debug, Clone, Serialize, Deserialize)]
512pub struct PlanStep {
513 pub order: usize,
514 pub tool: String,
515 pub command: String,
516 pub reason: String,
517 pub expected_tokens: usize,
518}
519
520impl Display for PlanQueryResult {
521 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522 writeln!(f, "Plan for {:?} ({})", self.query, self.inferred_intent)?;
523 for step in &self.steps {
524 writeln!(
525 f,
526 " {}. {} ~{} tokens",
527 step.order, step.command, step.expected_tokens
528 )?;
529 writeln!(f, " {}", step.reason)?;
530 }
531 write_meta(f, &self.meta)
532 }
533}
534
535#[derive(Debug, Clone, Serialize, Deserialize)]
536pub struct EditPrepResult {
537 pub symbol: String,
538 pub validate: ValidateResult,
539 pub signature: SignatureResult,
540 pub siblings: SiblingsResult,
541 pub context: ContextPack,
542 pub tests: TestsForResult,
543 pub next_steps: Vec<PlanStep>,
544 pub meta: QueryMeta,
545}
546
547impl Display for EditPrepResult {
548 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
549 writeln!(f, "Edit prep for {}", self.symbol)?;
550 writeln!(f, " exists: {}", self.validate.exists)?;
551 if let Some(symbol) = &self.signature.symbol {
552 writeln!(
553 f,
554 " target: {} {} @ {}:{}",
555 symbol.kind, symbol.qualified_name, symbol.path, symbol.start_line
556 )?;
557 }
558 writeln!(
559 f,
560 " context: body={} deps={} callers={} tests={}",
561 self.context.body.is_some(),
562 self.context.dependency_signatures.len(),
563 self.context.caller_signatures.len(),
564 self.tests.tests.len()
565 )?;
566 if !self.siblings.siblings.is_empty() {
567 writeln!(f, " siblings:")?;
568 for sibling in self.siblings.siblings.iter().take(5) {
569 writeln!(
570 f,
571 " {} ({} shared callers)",
572 sibling.qualified_name, sibling.shared_callers
573 )?;
574 }
575 }
576 if !self.next_steps.is_empty() {
577 writeln!(f, "\nNext steps:")?;
578 for step in &self.next_steps {
579 writeln!(f, " {}. {}", step.order, step.command)?;
580 }
581 }
582 write_meta(f, &self.meta)
583 }
584}
585
586#[derive(Debug, Clone, Serialize, Deserialize)]
587pub struct DiffImpactResult {
588 pub from_ref: String,
589 pub to_ref: String,
590 pub changed_files: usize,
591 pub changed_symbols: Vec<DiffChangedSymbol>,
592 pub impacted: Vec<DiffImpactedSymbol>,
593 pub meta: QueryMeta,
594}
595
596#[derive(Debug, Clone, Serialize, Deserialize)]
597pub struct DiffChangedSymbol {
598 pub symbol: SymbolRecord,
599 pub added_lines: usize,
600 pub removed_lines: usize,
601}
602
603#[derive(Debug, Clone, Serialize, Deserialize)]
604pub struct DiffImpactedSymbol {
605 pub symbol: SymbolRecord,
606 pub via: String,
607 pub criticality: f32,
608}
609
610impl Display for DiffImpactResult {
611 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
612 writeln!(
613 f,
614 "Diff impact {}..{} · {} changed files, {} changed symbols, {} impacted",
615 self.from_ref,
616 self.to_ref,
617 self.changed_files,
618 self.changed_symbols.len(),
619 self.impacted.len()
620 )?;
621 if !self.changed_symbols.is_empty() {
622 writeln!(f, "\n# changed")?;
623 for c in &self.changed_symbols {
624 writeln!(
625 f,
626 " +{:>3} -{:<3} {} @ {}:{}",
627 c.added_lines,
628 c.removed_lines,
629 c.symbol.qualified_name,
630 c.symbol.path,
631 c.symbol.start_line
632 )?;
633 }
634 }
635 if !self.impacted.is_empty() {
636 writeln!(f, "\n# impacted callers (top {})", self.impacted.len())?;
637 for i in &self.impacted {
638 writeln!(
639 f,
640 " score {:>5.1} {} (via {}) @ {}:{}",
641 i.criticality,
642 i.symbol.qualified_name,
643 i.via,
644 i.symbol.path,
645 i.symbol.start_line
646 )?;
647 }
648 }
649 write_meta(f, &self.meta)
650 }
651}
652
653#[derive(Debug, Clone, Serialize, Deserialize)]
654pub struct ImportRecord {
655 pub source: String,
656 pub from_path: String,
657 pub line: usize,
658 pub kind: String,
659}
660
661#[derive(Debug, Clone, Serialize, Deserialize)]
662pub struct ImportsResult {
663 pub path: String,
664 pub imports: Vec<ImportRecord>,
665 pub meta: QueryMeta,
666}
667
668impl Display for ImportsResult {
669 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
670 if self.imports.is_empty() {
671 writeln!(f, "No imports under {}.", self.path)?;
672 } else {
673 writeln!(f, "Imports for {}", self.path)?;
674 for imp in &self.imports {
675 writeln!(f, " {} ({}) @ line {}", imp.source, imp.kind, imp.line)?;
676 }
677 }
678 write_meta(f, &self.meta)
679 }
680}
681
682#[derive(Debug, Clone, Serialize, Deserialize)]
683pub struct ImportedByResult {
684 pub source: String,
685 pub importers: Vec<ImportRecord>,
686 pub meta: QueryMeta,
687}
688
689impl Display for ImportedByResult {
690 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
691 if self.importers.is_empty() {
692 writeln!(f, "Nothing imports {}.", self.source)?;
693 } else {
694 writeln!(
695 f,
696 "{} is imported by ({}):",
697 self.source,
698 self.importers.len()
699 )?;
700 for imp in &self.importers {
701 writeln!(f, " {} @ line {}", imp.from_path, imp.line)?;
702 }
703 }
704 write_meta(f, &self.meta)
705 }
706}
707
708#[derive(Debug, Clone, Serialize, Deserialize)]
709pub struct SignatureResult {
710 pub symbol: Option<SymbolRecord>,
711 pub members: Vec<SignatureLine>,
712 pub meta: QueryMeta,
713}
714
715impl Display for SignatureResult {
716 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
717 let Some(symbol) = &self.symbol else {
718 return writeln!(f, "No symbol found.");
719 };
720 writeln!(
721 f,
722 "{} {} @ {}:{}",
723 symbol.kind, symbol.qualified_name, symbol.path, symbol.start_line
724 )?;
725 if !symbol.signature.is_empty() {
726 writeln!(f, " {}", symbol.signature)?;
727 }
728 if !self.members.is_empty() {
729 writeln!(f, " members:")?;
730 for m in &self.members {
731 writeln!(f, " {:<9} {}", m.kind, m.signature)?;
732 }
733 }
734 write_meta(f, &self.meta)
735 }
736}
737
738#[derive(Debug, Clone, Serialize, Deserialize)]
739pub struct SiblingsResult {
740 pub symbol: String,
741 pub siblings: Vec<Sibling>,
742 pub meta: QueryMeta,
743}
744
745#[derive(Debug, Clone, Serialize, Deserialize)]
746pub struct Sibling {
747 pub qualified_name: String,
748 pub shared_callers: usize,
749 pub path: Option<String>,
750 pub line: Option<usize>,
751}
752
753impl Display for SiblingsResult {
754 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
755 if self.siblings.is_empty() {
756 writeln!(f, "No siblings found for {}.", self.symbol)?;
757 } else {
758 writeln!(f, "Siblings of {} (by shared callers)", self.symbol)?;
759 for s in &self.siblings {
760 let where_ = match (&s.path, s.line) {
761 (Some(p), Some(l)) => format!("{p}:{l}"),
762 _ => "—".to_string(),
763 };
764 writeln!(
765 f,
766 " {:>3} {} @ {}",
767 s.shared_callers, s.qualified_name, where_
768 )?;
769 }
770 }
771 write_meta(f, &self.meta)
772 }
773}
774
775#[derive(Debug, Clone, Default, Serialize, Deserialize)]
776pub struct SearchOptions {
777 pub kinds: Vec<String>,
780 pub languages: Vec<String>,
782 pub exported: Option<bool>,
784 pub path_prefix: Option<String>,
786 pub limit: usize,
788}
789
790#[derive(Debug, Clone, Default, Serialize, Deserialize)]
791pub struct UnusedOptions {
792 pub kinds: Vec<String>,
795 pub languages: Vec<String>,
797 pub exported: Option<bool>,
799 pub path_prefix: Option<String>,
801 pub limit: usize,
803}
804
805#[derive(Debug, Clone, Serialize, Deserialize)]
806pub struct UnusedSymbol {
807 pub symbol: SymbolRecord,
808 pub inbound_refs: usize,
809 pub inbound_edges: usize,
810}
811
812#[derive(Debug, Clone, Serialize, Deserialize)]
813pub struct UnusedResult {
814 pub symbols: Vec<UnusedSymbol>,
815 pub meta: QueryMeta,
816}
817
818impl Display for UnusedResult {
819 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
820 if self.symbols.is_empty() {
821 writeln!(f, "No unused symbols found.")?;
822 writeln!(
823 f,
824 " hint: relax filters such as `--kind`, `--language`, `--path`, or `--exported=false`."
825 )?;
826 } else {
827 writeln!(f, "Unused symbols ({}):", self.symbols.len())?;
828 for unused in &self.symbols {
829 let symbol = &unused.symbol;
830 writeln!(
831 f,
832 " {:<9} {} at {}:{}",
833 symbol.kind, symbol.qualified_name, symbol.path, symbol.start_line
834 )?;
835 }
836 }
837 write_meta(f, &self.meta)
838 }
839}
840
841#[derive(Debug, Clone, Serialize, Deserialize)]
842pub struct SearchHit {
843 pub symbol: SymbolRecord,
844 pub score: f32,
845}
846
847#[derive(Debug, Clone, Serialize, Deserialize)]
848pub struct SearchResult {
849 pub query: String,
850 pub hits: Vec<SearchHit>,
851 pub meta: QueryMeta,
852}
853
854impl Display for SearchResult {
855 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
856 if self.hits.is_empty() {
857 writeln!(f, "No symbols matched {:?}.", self.query)?;
858 writeln!(
859 f,
860 " hint: try a wider pattern such as `*{}*`, remove filters, or run `tessera validate {}` for near-misses.",
861 self.query, self.query
862 )?;
863 } else {
864 writeln!(f, "Search for {:?} ({} hits)", self.query, self.hits.len())?;
865 for hit in &self.hits {
866 writeln!(
867 f,
868 " {:>4.2} {:<9} {:<40} {}:{}",
869 hit.score,
870 hit.symbol.kind,
871 hit.symbol.qualified_name,
872 hit.symbol.path,
873 hit.symbol.start_line
874 )?;
875 }
876 }
877 write_meta(f, &self.meta)
878 }
879}
880
881#[derive(Debug, Clone, Serialize, Deserialize)]
882pub struct StatsResult {
883 pub files: usize,
884 pub symbols: usize,
885 pub references: usize,
886 pub edges: usize,
887 pub languages: Vec<LanguageCount>,
888 pub kinds: Vec<KindCount>,
889 pub top_fanout: Vec<TopFanout>,
890 pub db_path: String,
891 pub snapshot_present: bool,
892 pub meta: QueryMeta,
893}
894
895impl Display for StatsResult {
896 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
897 writeln!(
898 f,
899 "Tessera index at {} (snapshot={})",
900 self.db_path, self.snapshot_present
901 )?;
902 writeln!(
903 f,
904 " files={} symbols={} references={} edges={}",
905 self.files, self.symbols, self.references, self.edges
906 )?;
907 if !self.languages.is_empty() {
908 write!(f, " languages: ")?;
909 let parts: Vec<String> = self
910 .languages
911 .iter()
912 .map(|lc| format!("{}={}", lc.language, lc.count))
913 .collect();
914 writeln!(f, "{}", parts.join(", "))?;
915 }
916 if !self.kinds.is_empty() {
917 write!(f, " kinds: ")?;
918 let parts: Vec<String> = self
919 .kinds
920 .iter()
921 .map(|kc| format!("{}={}", kc.kind, kc.count))
922 .collect();
923 writeln!(f, "{}", parts.join(", "))?;
924 }
925 if !self.top_fanout.is_empty() {
926 writeln!(f, " top fanout:")?;
927 for tf in &self.top_fanout {
928 writeln!(f, " {:>4} {}", tf.callers, tf.qualified_name)?;
929 }
930 }
931 write_meta(f, &self.meta)
932 }
933}
934
935#[derive(Debug, Clone, Serialize, Deserialize)]
936pub struct LanguageCount {
937 pub language: String,
938 pub count: usize,
939}
940
941#[derive(Debug, Clone, Serialize, Deserialize)]
942pub struct KindCount {
943 pub kind: String,
944 pub count: usize,
945}
946
947#[derive(Debug, Clone, Serialize, Deserialize)]
948pub struct TopFanout {
949 pub qualified_name: String,
950 pub callers: usize,
951}
952
953#[derive(Debug, Clone, Serialize, Deserialize)]
954pub struct TestsForResult {
955 pub symbol: String,
956 pub tests: Vec<SymbolRecord>,
957 pub meta: QueryMeta,
958}
959
960impl Display for TestsForResult {
961 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
962 if self.tests.is_empty() {
963 writeln!(f, "No tests transitively touch {}.", self.symbol)?;
964 } else {
965 writeln!(f, "Tests touching {} ({}):", self.symbol, self.tests.len())?;
966 for test in &self.tests {
967 writeln!(
968 f,
969 " {} at {}:{}",
970 test.qualified_name, test.path, test.start_line
971 )?;
972 }
973 }
974 write_meta(f, &self.meta)
975 }
976}
977
978#[derive(Debug, Clone, Serialize, Deserialize)]
979pub struct BenchResult {
980 pub path: String,
981 pub files: usize,
982 pub symbols: usize,
983 pub references: usize,
984 pub index_full_ms: u128,
985 pub index_incremental_ms: u128,
986 pub queries: Vec<BenchQuery>,
987 pub savings: Vec<BenchSavings>,
988 pub chart: String,
989}
990
991impl Display for BenchResult {
992 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
993 writeln!(f, "{}", self.chart)
994 }
995}
996
997#[derive(Debug, Clone, Serialize, Deserialize)]
998pub struct BenchQuery {
999 pub name: String,
1000 pub ms: u128,
1001 pub tokens: usize,
1002}
1003
1004#[derive(Debug, Clone, Serialize, Deserialize)]
1005pub struct BenchSavings {
1006 pub label: String,
1007 pub raw_tokens: usize,
1008 pub tessera_tokens: usize,
1009 pub ratio: f32,
1010}
1011
1012#[derive(Debug, Clone, Serialize, Deserialize)]
1013pub struct PathNode {
1014 pub qualified_name: String,
1015 pub kind: String,
1016 pub path: String,
1017 pub line: usize,
1018}
1019
1020#[derive(Debug, Clone, Serialize, Deserialize)]
1021pub struct ConnectResult {
1022 pub from: String,
1023 pub to: String,
1024 pub found: bool,
1025 pub path: Vec<PathNode>,
1027 pub meta: QueryMeta,
1028}
1029
1030impl Display for ConnectResult {
1031 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1032 if !self.found {
1033 writeln!(
1034 f,
1035 "No call path from {} to {} (within search depth).",
1036 self.from, self.to
1037 )?;
1038 return write_meta(f, &self.meta);
1039 }
1040 writeln!(
1041 f,
1042 "Call path {} → {} ({} hops)",
1043 self.from,
1044 self.to,
1045 self.path.len().saturating_sub(1)
1046 )?;
1047 for (i, node) in self.path.iter().enumerate() {
1048 let arrow = if i == 0 { " " } else { " ↳ " };
1049 writeln!(
1050 f,
1051 "{}{} @ {}:{}",
1052 arrow, node.qualified_name, node.path, node.line
1053 )?;
1054 }
1055 write_meta(f, &self.meta)
1056 }
1057}
1058
1059#[derive(Debug, Clone, Serialize, Deserialize)]
1060pub struct ExportResult {
1061 pub format: String,
1062 pub scope: String,
1063 pub group_by: String,
1064 pub nodes: usize,
1065 pub edges: usize,
1066 pub truncated: bool,
1067 pub diagram: String,
1069 #[serde(skip_serializing_if = "Option::is_none")]
1070 pub html_path: Option<String>,
1071 pub meta: QueryMeta,
1072}
1073
1074impl Display for ExportResult {
1075 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1076 write!(f, "{}", self.diagram)
1079 }
1080}
1081
1082fn write_meta(f: &mut fmt::Formatter<'_>, meta: &QueryMeta) -> fmt::Result {
1083 writeln!(
1084 f,
1085 "\n_meta: ~{} tokens returned; alternatives: {}",
1086 meta.tokens_returned_estimate,
1087 meta.alternative_queries
1088 .iter()
1089 .map(|query| format!(
1090 "{} (~{} tokens, {:.2} fidelity)",
1091 query.tool, query.tokens_estimate, query.fidelity
1092 ))
1093 .collect::<Vec<_>>()
1094 .join(", ")
1095 )
1096}