Skip to main content

pathfinder_lib/server/
types.rs

1//! Tool parameter and response types for Pathfinder MCP tools.
2//!
3//! These structs are deserialized by the rmcp framework from MCP tool call
4//! payloads. The `dead_code` lint fires for param struct fields that are read
5//! by serde (via `Deserialize`) but never accessed by name in production code.
6//! A module-level `#![allow]` is used here so that each newly implemented tool
7//! can remove its struct's allow without touching unrelated items.
8#![allow(dead_code)] // Fields are read by serde deserialization, not by name
9
10use rmcp::schemars;
11use rmcp::serde::{self, Serialize};
12
13// ── Tool Parameter Types ────────────────────────────────────────────
14
15/// Parameters for `search_codebase`.
16#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
17pub struct SearchCodebaseParams {
18    /// Search pattern (literal or regex).
19    pub query: String,
20    /// Treat query as regex.
21    #[serde(default)]
22    pub is_regex: bool,
23    /// Limit search scope (e.g., `src/**/*.ts`).
24    #[serde(default = "default_path_glob")]
25    pub path_glob: String,
26    /// Filter mode: `code_only`, `comments_only`, or `all`.
27    ///
28    /// Uses Tree-sitter node classification to filter matches by context.
29    /// Defaults to `code_only` (exclude comments and string literals).
30    #[serde(default)]
31    pub filter_mode: pathfinder_common::types::FilterMode,
32    /// Maximum matches returned.
33    #[serde(default = "default_max_results")]
34    pub max_results: u32,
35    /// Lines of context above/below each match.
36    #[serde(default = "default_context_lines")]
37    pub context_lines: u32,
38    /// File paths already in the agent's context.
39    ///
40    /// For matches in these files, only minimal metadata is returned
41    /// (`file`, `line`, `column`, `enclosing_semantic_path`, `version_hash`).
42    /// The full `content` and context lines are omitted to save tokens.
43    #[serde(default)]
44    pub known_files: Vec<String>,
45    /// Group matches by file in the response.
46    ///
47    /// When `true`, the response includes `file_groups` instead of (or in addition to)
48    /// the flat `matches` list. Each group contains all matches for one file with a
49    /// single `version_hash` at group level.
50    #[serde(default)]
51    pub group_by_file: bool,
52    /// Glob pattern for files to exclude from search (e.g., `**/*.test.*`).
53    ///
54    /// Applied before search — not as a post-filter — so excluded files are
55    /// never read. Can be combined with `path_glob` include patterns.
56    #[serde(default)]
57    pub exclude_glob: String,
58}
59
60/// Parameters for `get_repo_map`.
61#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
62pub struct GetRepoMapParams {
63    /// Directory to map.
64    #[serde(default = "default_repo_map_path")]
65    pub path: String,
66    /// Total token budget for the entire skeleton output. Default: 16000.
67    ///
68    /// When `coverage_percent` in the response is low, increase this value
69    /// to include more files in the map.
70    #[serde(default = "default_max_tokens")]
71    pub max_tokens: u32,
72    /// Max directory traversal depth (default: 5).
73    ///
74    /// Increase this value when `coverage_percent` in the response is low
75    /// or when your project has deeply-nested source files (e.g. a depth 6+
76    /// monorepo). The walker stops early on shallow repos, so over-provisioning
77    /// is safe and nearly free.
78    #[serde(default = "default_depth")]
79    pub depth: u32,
80    /// Visibility filter: `public` or `all`.
81    #[serde(default)]
82    pub visibility: pathfinder_common::types::Visibility,
83    /// Import inclusion: `none`, `third_party`, or `all`.
84    #[serde(default)]
85    pub include_imports: pathfinder_common::types::IncludeImports,
86    /// Per-file token cap before a file skeleton is collapsed to a summary stub.
87    ///
88    /// When the rendered skeleton of an individual file exceeds this limit, the
89    /// file is replaced with a truncated stub showing only class/struct names and
90    /// method counts. Increase this value when files show `[TRUNCATED DUE TO SIZE]`
91    /// in the output. Default: 2000.
92    #[serde(default = "default_max_tokens_per_file")]
93    pub max_tokens_per_file: u32,
94    /// Git ref or duration to show only recently modified files (e.g., `HEAD~5`, `3h`, `2024-01-01`).
95    #[serde(default)]
96    pub changed_since: String,
97    /// Only include files with these extensions. Mutually exclusive with `exclude_extensions`.
98    #[serde(default)]
99    pub include_extensions: Vec<String>,
100    /// Exclude files with these extensions. Mutually exclusive with `include_extensions`.
101    #[serde(default)]
102    pub exclude_extensions: Vec<String>,
103}
104
105/// Parameters for `read_symbol_scope`.
106#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
107pub struct ReadSymbolScopeParams {
108    /// Semantic path (e.g., `src/auth.ts::AuthService.login`).
109    pub semantic_path: String,
110}
111
112/// Parameters for `read_with_deep_context`.
113#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
114pub struct ReadWithDeepContextParams {
115    /// Semantic path (e.g., `src/auth.ts::AuthService.login`). MUST include file path and '::'.
116    pub semantic_path: String,
117}
118
119/// Parameters for `get_definition`.
120#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
121pub struct GetDefinitionParams {
122    /// Semantic path to the reference (e.g., `src/auth.ts::AuthService.login`).
123    pub semantic_path: String,
124}
125
126/// Parameters for `analyze_impact`.
127#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
128pub struct AnalyzeImpactParams {
129    /// Semantic path to the target (e.g., `src/mod.rs::func`).
130    pub semantic_path: String,
131    /// Traversal depth (max: 5).
132    #[serde(default = "default_max_depth")]
133    pub max_depth: u32,
134}
135
136/// Parameters for `replace_body`.
137#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
138pub struct ReplaceBodyParams {
139    /// Full semantic path to the target (e.g., `src/mod.rs::func`).
140    pub semantic_path: String,
141    /// SHA-256 hash from previous read (OCC).
142    pub base_version: String,
143    /// Replacement body content (without outer braces).
144    pub new_code: String,
145    /// Write to disk even if validation fails.
146    #[serde(default)]
147    pub ignore_validation_failures: bool,
148}
149
150/// Parameters for `replace_full`.
151#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
152pub struct ReplaceFullParams {
153    /// Full semantic path to the target (e.g., `src/mod.rs::func`).
154    pub semantic_path: String,
155    /// SHA-256 hash from previous read (OCC).
156    pub base_version: String,
157    /// Complete replacement declaration.
158    pub new_code: String,
159    /// Write to disk even if validation fails.
160    #[serde(default)]
161    pub ignore_validation_failures: bool,
162}
163
164/// Parameters for `insert_before`.
165#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
166pub struct InsertBeforeParams {
167    /// Full semantic path or bare file path for BOF (e.g., `src/mod.rs::func` or `src/mod.rs`).
168    pub semantic_path: String,
169    /// SHA-256 hash from previous read (OCC).
170    pub base_version: String,
171    /// Code block to insert.
172    pub new_code: String,
173    /// Write to disk even if validation fails.
174    #[serde(default)]
175    pub ignore_validation_failures: bool,
176}
177
178/// Parameters for `insert_after`.
179#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
180pub struct InsertAfterParams {
181    /// Full semantic path or bare file path for EOF (e.g., `src/mod.rs::func` or `src/mod.rs`).
182    pub semantic_path: String,
183    /// SHA-256 hash from previous read (OCC).
184    pub base_version: String,
185    /// Code block to insert.
186    pub new_code: String,
187    /// Write to disk even if validation fails.
188    #[serde(default)]
189    pub ignore_validation_failures: bool,
190}
191
192/// Parameters for `delete_symbol`.
193#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
194pub struct DeleteSymbolParams {
195    /// Full semantic path to the target (e.g., `src/auth.ts::AuthService.login`).
196    pub semantic_path: String,
197    /// SHA-256 hash from previous read (OCC).
198    pub base_version: String,
199    /// Write to disk even if validation fails.
200    #[serde(default)]
201    pub ignore_validation_failures: bool,
202}
203
204/// Parameters for `validate_only`.
205#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
206pub struct ValidateOnlyParams {
207    /// Full semantic path to the target (e.g., `src/mod.rs::func`).
208    pub semantic_path: String,
209    /// Edit type: `replace_body`, `replace_full`, `insert_before`, `insert_after`, or `delete`.
210    pub edit_type: String,
211    /// Replacement code (required for all types except `delete`).
212    pub new_code: Option<String>,
213    /// SHA-256 hash from previous read (OCC).
214    pub base_version: String,
215}
216
217/// Parameters for `create_file`.
218#[derive(Debug, Default, Clone, serde::Deserialize, schemars::JsonSchema)]
219pub struct CreateFileParams {
220    /// Relative file path.
221    pub filepath: String,
222    /// Initial file content.
223    pub content: String,
224}
225
226/// Parameters for `delete_file`.
227#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
228pub struct DeleteFileParams {
229    /// Relative file path.
230    pub filepath: String,
231    /// SHA-256 hash from previous read (OCC).
232    pub base_version: String,
233}
234
235/// Parameters for `read_file`.
236#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
237pub struct ReadFileParams {
238    /// Relative file path.
239    pub filepath: String,
240    /// First line to return (1-indexed).
241    #[serde(default = "default_start_line")]
242    pub start_line: u32,
243    /// Maximum lines to return.
244    #[serde(default = "default_max_lines")]
245    pub max_lines: u32,
246}
247
248/// Parameters for `write_file`.
249#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
250pub struct WriteFileParams {
251    /// Relative file path.
252    pub filepath: String,
253    /// SHA-256 hash from previous read (OCC).
254    pub base_version: String,
255    /// Full replacement content. Mutually exclusive with `replacements`.
256    pub content: Option<String>,
257    /// Search-and-replace operations. Mutually exclusive with `content`.
258    pub replacements: Option<Vec<Replacement>>,
259}
260
261/// A search-and-replace operation for `write_file`.
262#[derive(Debug, Default, Clone, serde::Deserialize, schemars::JsonSchema)]
263pub struct Replacement {
264    /// Exact text to find.
265    pub old_text: String,
266    /// Replacement text.
267    pub new_text: String,
268}
269
270/// Parameters for `read_source_file`.
271#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
272pub struct ReadSourceFileParams {
273    /// Relative file path.
274    pub filepath: String,
275    /// Detail level: "compact", "symbols", "full".
276    #[serde(default = "default_detail_level")]
277    pub detail_level: String,
278    /// First line to return (1-indexed).
279    #[serde(default = "default_start_line")]
280    pub start_line: u32,
281    /// Last line to return (1-indexed, inclusive).
282    #[serde(default)]
283    pub end_line: Option<u32>,
284}
285
286/// A single edit in a `replace_batch` call.
287///
288/// Each edit specifies **either** semantic targeting (Option A) OR text targeting (Option B):
289///
290/// **Option A — Semantic targeting:** Set `semantic_path`, `edit_type`, and optionally `new_code`.
291/// Use for source-code constructs that have a parseable AST symbol.
292///
293/// **Option B — Text targeting:** Set `old_text`, `context_line`, and optionally `replacement_text`.
294/// Use for Vue `<template>`/`<style>` zones or any region with no usable semantic path.
295/// The search scans ±25 lines around `context_line` for an exact match of `old_text`.
296#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
297pub struct BatchEdit {
298    // ── Semantic targeting (Option A) ─────────────────────────────────────
299    /// Full semantic path to the target (e.g., `file.vue::script::check`).
300    /// Required when using semantic targeting.
301    #[serde(default)]
302    pub semantic_path: String,
303    /// Edit type: `replace_body`, `replace_full`, `insert_before`, `insert_after`, or `delete`.
304    /// Required when using semantic targeting.
305    #[serde(default)]
306    pub edit_type: String,
307    /// Replacement code (required for all semantic types except `delete`).
308    pub new_code: Option<String>,
309
310    // ── Text targeting (Option B) ──────────────────────────────────────────
311    /// Exact text to find and replace. Set this for template/style edits that have no
312    /// semantic path (e.g., Vue `<template>`, `<style>` zones, embedded SQL).
313    /// When set, `semantic_path` and `edit_type` are ignored.
314    /// The search scans ±25 lines around `context_line` for an exact match.
315    pub old_text: Option<String>,
316    /// Line number (1-indexed) to anchor the `old_text` search window.
317    /// Required when `old_text` is set. The search scans ±25 lines around this line.
318    pub context_line: Option<u32>,
319    /// Replacement text when using text targeting. Required when `old_text` is set.
320    pub replacement_text: Option<String>,
321    // ── Shared options ─────────────────────────────────────────────────────
322    /// When `true`, collapses `\s+` to a single space before matching `old_text`.
323    /// Useful for HTML/template contexts where indentation may be inconsistent.
324    /// Do NOT use for Python, YAML, or other whitespace-significant languages.
325    #[serde(default)]
326    pub normalize_whitespace: bool,
327}
328
329/// Parameters for `replace_batch`.
330#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
331pub struct ReplaceBatchParams {
332    /// Relative file path.
333    pub filepath: String,
334    /// SHA-256 hash from previous read (OCC) for the entire file.
335    pub base_version: String,
336    /// List of edits to apply atomically.
337    pub edits: Vec<BatchEdit>,
338    /// Write to disk even if validation fails.
339    #[serde(default)]
340    pub ignore_validation_failures: bool,
341}
342
343// ── Response Types ──────────────────────────────────────────────────
344
345/// The response for `search_codebase`.
346#[derive(Debug, Serialize, schemars::JsonSchema)]
347pub struct SearchCodebaseResponse {
348    /// List of search matches.
349    pub matches: Vec<pathfinder_search::SearchMatch>,
350    /// Total number of matches found.
351    pub total_matches: usize,
352    /// Indicates if the match list was truncated.
353    pub truncated: bool,
354    /// Grouped output — populated when `group_by_file: true`.
355    ///
356    /// Each group represents one file and contains either full matches (for
357    /// unknown files) or minimal matches (for files in `known_files`).
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub file_groups: Option<Vec<SearchResultGroup>>,
360    /// Whether the search response is degraded.
361    #[serde(default)]
362    pub degraded: bool,
363    /// Reason for degradation, if any.
364    #[serde(skip_serializing_if = "Option::is_none")]
365    pub degraded_reason: Option<String>,
366}
367
368/// A minimal match entry for files already in the agent's context (`known_files`)
369/// when grouped by file.
370///
371/// Omits `file`, `version_hash` (deduplicated at group level), and `content`,
372/// `context_before`, and `context_after` to save tokens.
373#[derive(Debug, Serialize, schemars::JsonSchema)]
374pub struct GroupedKnownMatch {
375    /// 1-indexed line number.
376    pub line: u64,
377    /// 1-indexed column number.
378    pub column: u64,
379    /// AST symbol enclosing this match (if available).
380    #[serde(skip_serializing_if = "Option::is_none")]
381    pub enclosing_semantic_path: Option<String>,
382    /// Always `true` — signals this match was suppressed because the file is known.
383    pub known: bool,
384}
385
386/// A group of matches belonging to one file, returned when `group_by_file: true`.
387#[derive(Debug, Serialize, schemars::JsonSchema)]
388pub struct SearchResultGroup {
389    /// File path relative to workspace root.
390    pub file: String,
391    /// SHA-256 hash of the file (shared by all matches in this group).
392    pub version_hash: String,
393    /// Full matches (for files NOT in `known_files`).
394    ///
395    /// Per-match objects contain only `{ line, column, content, context_before,
396    /// context_after, enclosing_semantic_path }` — `file` and `version_hash` are
397    /// deduplicated at group level to avoid repeating them for every match.
398    #[serde(skip_serializing_if = "Vec::is_empty")]
399    pub matches: Vec<GroupedMatch>,
400    /// Minimal matches (for files in `known_files`).
401    #[serde(skip_serializing_if = "Vec::is_empty")]
402    pub known_matches: Vec<GroupedKnownMatch>,
403}
404/// A single match within a `SearchResultGroup`.
405///
406/// Omits `file` and `version_hash` (deduplicated at group level) to reduce
407/// token usage when many matches belong to the same file.
408#[derive(Debug, Serialize, schemars::JsonSchema)]
409pub struct GroupedMatch {
410    /// 1-indexed line number of the match.
411    pub line: u64,
412    /// 1-indexed column number of the match start.
413    pub column: u64,
414    /// The full content of the matching line.
415    pub content: String,
416    /// Lines immediately before the match.
417    #[serde(skip_serializing_if = "Vec::is_empty")]
418    pub context_before: Vec<String>,
419    /// Lines immediately after the match.
420    #[serde(skip_serializing_if = "Vec::is_empty")]
421    pub context_after: Vec<String>,
422    /// AST symbol enclosing this match (if available).
423    #[serde(skip_serializing_if = "Option::is_none")]
424    pub enclosing_semantic_path: Option<String>,
425}
426
427/// The metadata embedded in `structured_content` for `get_repo_map`.
428#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
429pub struct GetRepoMapMetadata {
430    /// Technology stack of the repository.
431    pub tech_stack: Vec<String>,
432    /// Number of files scanned.
433    pub files_scanned: usize,
434    /// Number of files truncated.
435    pub files_truncated: usize,
436    /// Number of files within the configured scope.
437    pub files_in_scope: usize,
438    /// Percentage of files covered by the search.
439    pub coverage_percent: u8,
440    /// Map of file paths to their version hashes.
441    pub version_hashes: std::collections::HashMap<String, String>,
442    /// Always `true` while visibility filtering is not yet implemented.
443    /// Agents should treat all symbols as public regardless of `visibility` param.
444    #[serde(skip_serializing_if = "Option::is_none")]
445    pub visibility_degraded: Option<bool>,
446    /// `true` when filtering by `changed_since` fails (e.g., git is unavailable).
447    #[serde(default)]
448    pub degraded: bool,
449    /// Reason for degradation.
450    #[serde(skip_serializing_if = "Option::is_none")]
451    pub degraded_reason: Option<String>,
452    /// System capabilities available for this repository.
453    pub capabilities: RepoCapabilities,
454}
455
456/// The overall capabilities of the Pathfinder system.
457#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
458pub struct RepoCapabilities {
459    /// Whether AST-aware edit tools are supported.
460    pub edit: bool,
461    /// Whether the search engine is supported.
462    pub search: bool,
463    /// LSP-specific capabilities and status.
464    pub lsp: LspCapabilities,
465}
466
467/// LSP status and capabilities.
468#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
469pub struct LspCapabilities {
470    /// `true` if LSP is generally supported by the system.
471    pub supported: bool,
472    /// Map of language ID to its specific LSP process status.
473    pub per_language: std::collections::HashMap<String, pathfinder_lsp::types::LspLanguageStatus>,
474}
475
476/// The metadata embedded in `structured_content` for `read_symbol_scope`.
477#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
478pub struct ReadSymbolScopeMetadata {
479    /// The extracted symbol source code.
480    ///
481    /// Mirrors `content[0].text` in the MCP response. Provided here so that
482    /// agents consuming `structured_content` directly have the full source
483    /// without needing to inspect the main content array.
484    pub content: String,
485    /// Starting line number of the symbol in the source.
486    pub start_line: usize,
487    /// Ending line number of the symbol in the source.
488    pub end_line: usize,
489    /// Version hash of the file containing the symbol.
490    pub version_hash: String,
491    /// Programming language of the source symbol.
492    pub language: String,
493}
494
495/// The response for `read_symbol_scope`.
496#[derive(Debug, Serialize, schemars::JsonSchema)]
497pub struct ReadSymbolScopeResponse {
498    /// The extracted symbol source code.
499    pub content: String,
500    /// Starting line number of the symbol in the source.
501    pub start_line: usize,
502    /// Ending line number of the symbol in the source.
503    pub end_line: usize,
504    /// Version hash of the file containing the symbol.
505    pub version_hash: String,
506    /// Programming language of the source symbol.
507    pub language: String,
508}
509
510/// A symbol output for `read_source_file`.
511#[derive(Debug, Clone, PartialEq, Serialize, serde::Deserialize, schemars::JsonSchema)]
512pub struct SourceSymbol {
513    /// Name of the symbol.
514    pub name: String,
515    /// Semantic path of the symbol.
516    pub semantic_path: String,
517    /// Kind of the symbol (e.g., function, struct).
518    pub kind: String,
519    /// Starting line number of the symbol in the source.
520    pub start_line: usize,
521    /// Ending line number of the symbol in the source.
522    pub end_line: usize,
523    /// Child symbols nested within this symbol.
524    #[serde(skip_serializing_if = "Vec::is_empty")]
525    pub children: Vec<SourceSymbol>,
526}
527
528/// The metadata embedded in `structured_content` for `read_source_file`.
529#[derive(Debug, Clone, PartialEq, Default, Serialize, serde::Deserialize, schemars::JsonSchema)]
530pub struct ReadSourceFileMetadata {
531    /// Version hash of the source file.
532    pub version_hash: String,
533    /// Programming language of the source file.
534    pub language: String,
535    /// Symbols extracted from the source file.
536    #[serde(skip_serializing_if = "Vec::is_empty")]
537    pub symbols: Vec<SourceSymbol>,
538}
539
540/// The response for all AST-aware edit tools:
541/// `replace_body`, `replace_full`, `insert_before`, `insert_after`,
542/// `delete_symbol`, and `validate_only`.
543#[derive(Debug, Serialize, schemars::JsonSchema)]
544pub struct EditResponse {
545    /// Whether the edit succeeded (always `true` for non-`validate_only` tools).
546    pub success: bool,
547    /// SHA-256 hash of the file after the edit. `None` for `validate_only`.
548    #[serde(skip_serializing_if = "Option::is_none")]
549    pub new_version_hash: Option<String>,
550    /// Whether the code was reformatted (always `false` until LSP formatting is wired).
551    pub formatted: bool,
552    /// LSP validation result.
553    pub validation: EditValidation,
554    /// `true` when LSP validation was skipped (no language server available).
555    #[serde(default)]
556    pub validation_skipped: bool,
557    /// Machine-readable reason why validation was skipped.
558    #[serde(skip_serializing_if = "Option::is_none")]
559    pub validation_skipped_reason: Option<String>,
560}
561
562/// LSP validation result embedded in `EditResponse`.
563#[derive(Debug, Serialize, schemars::JsonSchema)]
564pub struct EditValidation {
565    /// `"passed"`, `"failed"`, or `"skipped"`.
566    pub status: String,
567    /// Errors introduced by the edit.
568    pub introduced_errors: Vec<pathfinder_common::error::DiagnosticError>,
569    /// Errors resolved by the edit.
570    pub resolved_errors: Vec<pathfinder_common::error::DiagnosticError>,
571}
572
573impl EditValidation {
574    /// Return a skipped validation result (no LSP available).
575    #[must_use]
576    pub fn skipped() -> Self {
577        Self {
578            status: "skipped".to_owned(),
579            introduced_errors: vec![],
580            resolved_errors: vec![],
581        }
582    }
583}
584
585/// The response for `create_file`.
586#[derive(Debug, Serialize, schemars::JsonSchema)]
587pub struct CreateFileResponse {
588    /// Whether the file creation succeeded.
589    pub success: bool,
590    /// Hash of the file version after creation.
591    pub version_hash: String,
592    /// Results of validation after file creation.
593    pub validation: ValidationResult,
594}
595
596/// The response for `delete_file`.
597#[derive(Debug, Serialize, schemars::JsonSchema)]
598pub struct DeleteFileResponse {
599    /// Whether the file deletion succeeded.
600    pub success: bool,
601}
602
603/// The metadata embedded in `structured_content` for `read_file`.
604#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
605pub struct ReadFileMetadata {
606    /// First line number returned from the file.
607    pub start_line: u32,
608    /// Number of lines returned.
609    pub lines_returned: u32,
610    /// Total number of lines in the file.
611    pub total_lines: u32,
612    /// Whether the output was truncated.
613    pub truncated: bool,
614    /// Version hash of the file.
615    pub version_hash: String,
616    /// Detected language of the file.
617    pub language: String,
618}
619
620/// The metadata embedded in `structured_content` for `write_file`.
621#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
622pub struct WriteFileMetadata {
623    /// Whether the write operation succeeded.
624    pub success: bool,
625    /// New version hash after writing.
626    pub new_version_hash: String,
627}
628
629/// Validation result for edits.
630#[derive(Debug, Serialize, schemars::JsonSchema)]
631pub struct ValidationResult {
632    /// Status of the validation (`"passed"`, `"failed"`, or `"skipped"`).
633    pub status: String,
634    /// Errors that were introduced in the edits.
635    pub introduced_errors: Vec<pathfinder_common::error::DiagnosticError>,
636}
637
638// ── Navigation Tool Response Types ─────────────────────────────────
639
640/// A dependency signature extracted for `read_with_deep_context`.
641#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
642pub struct DeepContextDependency {
643    /// Semantic path of the called symbol.
644    pub semantic_path: String,
645    /// Extracted signature (declaration line only, no body).
646    pub signature: String,
647    /// File path relative to workspace root.
648    pub file: String,
649    /// 1-indexed line of the definition.
650    pub line: usize,
651}
652
653/// The metadata embedded in `structured_content` for `read_with_deep_context`.
654#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
655pub struct ReadWithDeepContextMetadata {
656    /// Start line of the symbol (1-indexed).
657    pub start_line: usize,
658    /// End line of the symbol (1-indexed).
659    pub end_line: usize,
660    /// OCC version hash for the symbol's file.
661    pub version_hash: String,
662    /// Detected language.
663    pub language: String,
664    /// Signatures of all symbols called by this one.
665    #[serde(skip_serializing_if = "Vec::is_empty", default)]
666    pub dependencies: Vec<DeepContextDependency>,
667    /// `true` when LSP dependency resolution was unavailable (Tree-sitter only).
668    #[serde(default)]
669    pub degraded: bool,
670    /// Reason for degradation (e.g., `"no_lsp"`).
671    #[serde(skip_serializing_if = "Option::is_none")]
672    pub degraded_reason: Option<String>,
673}
674
675/// The response for `get_definition`.
676#[derive(Debug, Serialize, schemars::JsonSchema)]
677pub struct GetDefinitionResponse {
678    /// Relative file path of the definition site.
679    pub file: String,
680    /// 1-indexed line number of the definition.
681    pub line: u32,
682    /// 1-indexed column number.
683    pub column: u32,
684    /// First line of the definition (code preview).
685    pub preview: String,
686    /// `true` when LSP was unavailable and no fallback was possible.
687    #[serde(default)]
688    pub degraded: bool,
689    /// Reason for degradation.
690    #[serde(skip_serializing_if = "Option::is_none")]
691    pub degraded_reason: Option<String>,
692}
693
694/// A single reference in an impact analysis.
695#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
696pub struct ImpactReference {
697    /// Semantic path of the referencing/referenced symbol.
698    pub semantic_path: String,
699    /// File path relative to workspace root.
700    pub file: String,
701    /// 1-indexed line of the call site or definition.
702    pub line: usize,
703    /// A short code snippet showing the call site or declaration.
704    pub snippet: String,
705    /// OCC version hash for this file.
706    pub version_hash: String,
707    /// Direction of the reference relative to the target symbol.
708    ///
709    /// - `"incoming"` — this symbol calls or references the target (a caller).
710    /// - `"outgoing"` — the target calls or references this symbol (a callee).
711    /// - `"incoming_heuristic"` — inferred by grep fallback when LSP is unavailable;
712    ///   treat as a candidate, not a confirmed call.
713    pub direction: String,
714    /// BFS traversal depth (0 = direct caller/callee, 1 = one hop away, etc.).
715    pub depth: usize,
716}
717
718/// The metadata embedded in `structured_content` for `analyze_impact`.
719#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
720pub struct AnalyzeImpactMetadata {
721    /// Symbols that call the target (caller graph).
722    /// `null` when `degraded` is `true` — LSP was unavailable so callers are **unknown**.
723    /// An empty array `[]` means LSP confirmed zero callers.
724    pub incoming: Option<Vec<ImpactReference>>,
725    /// Symbols the target calls (callee graph).
726    /// `null` when `degraded` is `true` — LSP was unavailable so callees are **unknown**.
727    /// An empty array `[]` means LSP confirmed zero callees.
728    pub outgoing: Option<Vec<ImpactReference>>,
729    /// Number of transitive levels traversed.
730    pub depth_reached: u32,
731    /// Total files referenced across all incoming and outgoing references.
732    pub files_referenced: usize,
733    /// Whether the call hierarchy analysis was degraded (LSP unavailable or crashed).
734    /// Always present. When `true`, `incoming` and `outgoing` are `null` (not empty arrays).
735    pub degraded: bool,
736    /// Machine-readable reason for degradation (e.g., `no_lsp`, `lsp_crash`, `lsp_timeout`).
737    /// Absent when `degraded` is `false`.
738    #[serde(skip_serializing_if = "Option::is_none")]
739    pub degraded_reason: Option<String>,
740    /// SHA-256 version hashes for all referenced files (including the target file itself),
741    /// keyed by relative file path. Agents can use these as `base_version` for immediate
742    /// editing without a separate read call.
743    #[serde(skip_serializing_if = "std::collections::HashMap::is_empty", default)]
744    pub version_hashes: std::collections::HashMap<String, String>,
745}
746
747// ── Default Value Functions ─────────────────────────────────────────
748
749pub(crate) fn default_path_glob() -> String {
750    "**/*".to_owned()
751}
752pub(crate) fn default_max_results() -> u32 {
753    50
754}
755pub(crate) fn default_context_lines() -> u32 {
756    2
757}
758pub(crate) fn default_repo_map_path() -> String {
759    ".".to_owned()
760}
761pub(crate) fn default_max_tokens() -> u32 {
762    16_000
763}
764pub(crate) fn default_max_tokens_per_file() -> u32 {
765    2_000
766}
767pub(crate) fn default_depth() -> u32 {
768    5
769}
770pub(crate) fn default_max_depth() -> u32 {
771    2
772}
773pub(crate) fn default_start_line() -> u32 {
774    1
775}
776pub(crate) fn default_max_lines() -> u32 {
777    500
778}
779pub(crate) fn default_detail_level() -> String {
780    "compact".to_string()
781}