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 `insert_into`.
193#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
194pub struct InsertIntoParams {
195 /// Full semantic path to the container target (e.g., `src/lib.rs::tests`).
196 pub semantic_path: String,
197 /// SHA-256 hash from previous read (OCC).
198 pub base_version: String,
199 /// Code block to insert.
200 pub new_code: String,
201 /// Write to disk even if validation fails.
202 #[serde(default)]
203 pub ignore_validation_failures: bool,
204}
205
206/// Parameters for `delete_symbol`.
207#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
208pub struct DeleteSymbolParams {
209 /// Full semantic path to the target (e.g., `src/auth.ts::AuthService.login`).
210 pub semantic_path: String,
211 /// SHA-256 hash from previous read (OCC).
212 pub base_version: String,
213 /// Write to disk even if validation fails.
214 #[serde(default)]
215 pub ignore_validation_failures: bool,
216}
217
218/// Parameters for `validate_only`.
219#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
220pub struct ValidateOnlyParams {
221 /// Full semantic path to the target (e.g., `src/mod.rs::func`).
222 pub semantic_path: String,
223 /// Edit type: `replace_body`, `replace_full`, `insert_before`, `insert_after`, `insert_into`, or `delete`.
224 pub edit_type: String,
225 /// Replacement code (required for all types except `delete`).
226 pub new_code: Option<String>,
227 /// SHA-256 hash from previous read (OCC).
228 pub base_version: String,
229}
230
231/// Parameters for `create_file`.
232#[derive(Debug, Default, Clone, serde::Deserialize, schemars::JsonSchema)]
233pub struct CreateFileParams {
234 /// Relative file path.
235 pub filepath: String,
236 /// Initial file content.
237 pub content: String,
238}
239
240/// Parameters for `delete_file`.
241#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
242pub struct DeleteFileParams {
243 /// Relative file path.
244 pub filepath: String,
245 /// SHA-256 hash from previous read (OCC).
246 pub base_version: String,
247}
248
249/// Parameters for `read_file`.
250#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
251pub struct ReadFileParams {
252 /// Relative file path.
253 pub filepath: String,
254 /// First line to return (1-indexed).
255 #[serde(default = "default_start_line")]
256 pub start_line: u32,
257 /// Maximum lines to return.
258 #[serde(default = "default_max_lines")]
259 pub max_lines: u32,
260}
261
262/// Parameters for `write_file`.
263#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
264pub struct WriteFileParams {
265 /// Relative file path.
266 pub filepath: String,
267 /// SHA-256 hash from previous read (OCC).
268 pub base_version: String,
269 /// Full replacement content. Mutually exclusive with `replacements`.
270 pub content: Option<String>,
271 /// Search-and-replace operations. Mutually exclusive with `content`.
272 pub replacements: Option<Vec<Replacement>>,
273}
274
275/// A search-and-replace operation for `write_file`.
276#[derive(Debug, Default, Clone, serde::Deserialize, schemars::JsonSchema)]
277pub struct Replacement {
278 /// Exact text to find.
279 pub old_text: String,
280 /// Replacement text.
281 pub new_text: String,
282}
283
284/// Parameters for `read_source_file`.
285#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
286pub struct ReadSourceFileParams {
287 /// Relative file path.
288 pub filepath: String,
289 /// Detail level: "compact", "symbols", "full".
290 #[serde(default = "default_detail_level")]
291 pub detail_level: String,
292 /// First line to return (1-indexed).
293 #[serde(default = "default_start_line")]
294 pub start_line: u32,
295 /// Last line to return (1-indexed, inclusive).
296 #[serde(default)]
297 pub end_line: Option<u32>,
298}
299
300/// A single edit in a `replace_batch` call.
301///
302/// Each edit specifies **either** semantic targeting (Option A) OR text targeting (Option B):
303///
304/// **Option A — Semantic targeting:** Set `semantic_path`, `edit_type`, and optionally `new_code`.
305/// Use for source-code constructs that have a parseable AST symbol.
306///
307/// **Option B — Text targeting:** Set `old_text`, `context_line`, and optionally `replacement_text`.
308/// Use for Vue `<template>`/`<style>` zones or any region with no usable semantic path.
309/// The search scans ±25 lines around `context_line` for an exact match of `old_text`.
310#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
311pub struct BatchEdit {
312 // ── Semantic targeting (Option A) ─────────────────────────────────────
313 /// Full semantic path to the target (e.g., `file.vue::script::check`).
314 /// Required when using semantic targeting.
315 #[serde(default)]
316 pub semantic_path: String,
317 /// Edit type: `replace_body`, `replace_full`, `insert_before`, `insert_after`, or `delete`.
318 /// Required when using semantic targeting.
319 #[serde(default)]
320 pub edit_type: String,
321 /// Replacement code (required for all semantic types except `delete`).
322 pub new_code: Option<String>,
323
324 // ── Text targeting (Option B) ──────────────────────────────────────────
325 /// Exact text to find and replace. Set this for template/style edits that have no
326 /// semantic path (e.g., Vue `<template>`, `<style>` zones, embedded SQL).
327 /// When set, `semantic_path` and `edit_type` are ignored.
328 /// The search scans ±25 lines around `context_line` for an exact match.
329 pub old_text: Option<String>,
330 /// Line number (1-indexed) to anchor the `old_text` search window.
331 /// Required when `old_text` is set. The search scans ±25 lines around this line.
332 pub context_line: Option<u32>,
333 /// Replacement text when using text targeting. Required when `old_text` is set.
334 pub replacement_text: Option<String>,
335 // ── Shared options ─────────────────────────────────────────────────────
336 /// When `true`, collapses `\s+` to a single space before matching `old_text`.
337 /// Useful for HTML/template contexts where indentation may be inconsistent.
338 /// Do NOT use for Python, YAML, or other whitespace-significant languages.
339 #[serde(default)]
340 pub normalize_whitespace: bool,
341}
342
343/// Parameters for `replace_batch`.
344#[derive(Debug, Default, serde::Deserialize, schemars::JsonSchema)]
345pub struct ReplaceBatchParams {
346 /// Relative file path.
347 pub filepath: String,
348 /// SHA-256 hash from previous read (OCC) for the entire file.
349 pub base_version: String,
350 /// List of edits to apply atomically.
351 pub edits: Vec<BatchEdit>,
352 /// Write to disk even if validation fails.
353 #[serde(default)]
354 pub ignore_validation_failures: bool,
355}
356
357// ── Response Types ──────────────────────────────────────────────────
358
359/// The response for `search_codebase`.
360#[derive(Debug, Serialize, schemars::JsonSchema)]
361pub struct SearchCodebaseResponse {
362 /// List of search matches.
363 pub matches: Vec<pathfinder_search::SearchMatch>,
364 /// Raw match count from ripgrep **before** `filter_mode` filtering, **after** ripgrep truncation.
365 ///
366 /// When `truncated = true`, this equals `max_results` and ripgrep stopped searching early.
367 /// When `filter_mode` is `"comments_only"` or `"code_only"`, matches that do not
368 /// pass the filter are excluded from `matches` but still counted here.
369 /// Compare with `returned_count` to understand how many matches were filtered.
370 pub total_matches: usize,
371 /// Number of matches actually returned in this response (after `filter_mode` filtering).
372 ///
373 /// `returned_count == matches.len()`. Provided as a convenience field so agents
374 /// do not need to count `matches` themselves.
375 pub returned_count: usize,
376 /// Indicates if the match list was truncated by `max_results`.
377 pub truncated: bool,
378 /// Grouped output — populated when `group_by_file: true`.
379 ///
380 /// Each group represents one file and contains either full matches (for
381 /// unknown files) or minimal matches (for files in `known_files`).
382 #[serde(skip_serializing_if = "Option::is_none")]
383 pub file_groups: Option<Vec<SearchResultGroup>>,
384 /// Whether the search response is degraded.
385 #[serde(default)]
386 pub degraded: bool,
387 /// Reason for degradation, if any.
388 #[serde(skip_serializing_if = "Option::is_none")]
389 pub degraded_reason: Option<String>,
390}
391
392/// A minimal match entry for files already in the agent's context (`known_files`)
393/// when grouped by file.
394///
395/// Omits `file`, `version_hash` (deduplicated at group level), and `content`,
396/// `context_before`, and `context_after` to save tokens.
397#[derive(Debug, Serialize, schemars::JsonSchema)]
398pub struct GroupedKnownMatch {
399 /// 1-indexed line number.
400 pub line: u64,
401 /// 1-indexed column number.
402 pub column: u64,
403 /// AST symbol enclosing this match (if available).
404 #[serde(skip_serializing_if = "Option::is_none")]
405 pub enclosing_semantic_path: Option<String>,
406 /// Always `true` — signals this match was suppressed because the file is known.
407 pub known: bool,
408}
409
410/// A group of matches belonging to one file, returned when `group_by_file: true`.
411#[derive(Debug, Serialize, schemars::JsonSchema)]
412pub struct SearchResultGroup {
413 /// File path relative to workspace root.
414 pub file: String,
415 /// SHA-256 hash of the file (shared by all matches in this group).
416 pub version_hash: String,
417 /// Full matches (for files NOT in `known_files`).
418 ///
419 /// Per-match objects contain only `{ line, column, content, context_before,
420 /// context_after, enclosing_semantic_path }` — `file` and `version_hash` are
421 /// deduplicated at group level to avoid repeating them for every match.
422 #[serde(skip_serializing_if = "Vec::is_empty")]
423 pub matches: Vec<GroupedMatch>,
424 /// Minimal matches (for files in `known_files`).
425 #[serde(skip_serializing_if = "Vec::is_empty")]
426 pub known_matches: Vec<GroupedKnownMatch>,
427}
428/// A single match within a `SearchResultGroup`.
429///
430/// Omits `file` and `version_hash` (deduplicated at group level) to reduce
431/// token usage when many matches belong to the same file.
432#[derive(Debug, Serialize, schemars::JsonSchema)]
433pub struct GroupedMatch {
434 /// 1-indexed line number of the match.
435 pub line: u64,
436 /// 1-indexed column number of the match start.
437 pub column: u64,
438 /// The full content of the matching line.
439 pub content: String,
440 /// Lines immediately before the match.
441 #[serde(skip_serializing_if = "Vec::is_empty")]
442 pub context_before: Vec<String>,
443 /// Lines immediately after the match.
444 #[serde(skip_serializing_if = "Vec::is_empty")]
445 pub context_after: Vec<String>,
446 /// AST symbol enclosing this match (if available).
447 #[serde(skip_serializing_if = "Option::is_none")]
448 pub enclosing_semantic_path: Option<String>,
449}
450
451/// The metadata embedded in `structured_content` for `get_repo_map`.
452#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
453pub struct GetRepoMapMetadata {
454 /// Technology stack of the repository.
455 pub tech_stack: Vec<String>,
456 /// Number of files scanned.
457 pub files_scanned: usize,
458 /// Number of files truncated.
459 pub files_truncated: usize,
460 /// Number of files within the configured scope.
461 pub files_in_scope: usize,
462 /// Percentage of files covered by the search.
463 pub coverage_percent: u8,
464 /// Map of file paths to their version hashes.
465 pub version_hashes: std::collections::HashMap<String, String>,
466 /// Always `true` while visibility filtering is not yet implemented.
467 /// Agents should treat all symbols as public regardless of `visibility` param.
468 #[serde(skip_serializing_if = "Option::is_none")]
469 pub visibility_degraded: Option<bool>,
470 /// `true` when filtering by `changed_since` fails (e.g., git is unavailable).
471 #[serde(default)]
472 pub degraded: bool,
473 /// Reason for degradation.
474 #[serde(skip_serializing_if = "Option::is_none")]
475 pub degraded_reason: Option<String>,
476 /// System capabilities available for this repository.
477 pub capabilities: RepoCapabilities,
478}
479
480/// The overall capabilities of the Pathfinder system.
481#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
482pub struct RepoCapabilities {
483 /// Whether AST-aware edit tools are supported.
484 pub edit: bool,
485 /// Whether the search engine is supported.
486 pub search: bool,
487 /// LSP-specific capabilities and status.
488 pub lsp: LspCapabilities,
489}
490
491/// LSP status and capabilities.
492#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
493pub struct LspCapabilities {
494 /// `true` if LSP is generally supported by the system.
495 pub supported: bool,
496 /// Map of language ID to its specific LSP process status.
497 pub per_language: std::collections::HashMap<String, pathfinder_lsp::types::LspLanguageStatus>,
498}
499
500/// The metadata embedded in `structured_content` for `read_symbol_scope`.
501#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
502pub struct ReadSymbolScopeMetadata {
503 /// The extracted symbol source code.
504 ///
505 /// Mirrors `content[0].text` in the MCP response. Provided here so that
506 /// agents consuming `structured_content` directly have the full source
507 /// without needing to inspect the main content array.
508 pub content: String,
509 /// Starting line number of the symbol in the source.
510 pub start_line: usize,
511 /// Ending line number of the symbol in the source.
512 pub end_line: usize,
513 /// Version hash of the file containing the symbol.
514 pub version_hash: String,
515 /// Programming language of the source symbol.
516 pub language: String,
517}
518
519/// The response for `read_symbol_scope`.
520#[derive(Debug, Serialize, schemars::JsonSchema)]
521pub struct ReadSymbolScopeResponse {
522 /// The extracted symbol source code.
523 pub content: String,
524 /// Starting line number of the symbol in the source.
525 pub start_line: usize,
526 /// Ending line number of the symbol in the source.
527 pub end_line: usize,
528 /// Version hash of the file containing the symbol.
529 pub version_hash: String,
530 /// Programming language of the source symbol.
531 pub language: String,
532}
533
534/// A symbol output for `read_source_file`.
535#[derive(Debug, Clone, PartialEq, Serialize, serde::Deserialize, schemars::JsonSchema)]
536pub struct SourceSymbol {
537 /// Name of the symbol.
538 pub name: String,
539 /// Semantic path of the symbol.
540 pub semantic_path: String,
541 /// Kind of the symbol (e.g., function, struct).
542 pub kind: String,
543 /// Starting line number of the symbol in the source.
544 pub start_line: usize,
545 /// Ending line number of the symbol in the source.
546 pub end_line: usize,
547 /// Child symbols nested within this symbol.
548 #[serde(skip_serializing_if = "Vec::is_empty")]
549 pub children: Vec<Self>,
550}
551
552/// The metadata embedded in `structured_content` for `read_source_file`.
553#[derive(Debug, Clone, PartialEq, Default, Serialize, serde::Deserialize, schemars::JsonSchema)]
554pub struct ReadSourceFileMetadata {
555 /// Version hash of the source file.
556 pub version_hash: String,
557 /// Programming language of the source file.
558 pub language: String,
559 /// Symbols extracted from the source file.
560 #[serde(skip_serializing_if = "Vec::is_empty")]
561 pub symbols: Vec<SourceSymbol>,
562}
563
564/// The response for all AST-aware edit tools:
565/// `replace_body`, `replace_full`, `insert_before`, `insert_after`,
566/// `delete_symbol`, and `validate_only`.
567#[derive(Debug, Serialize, schemars::JsonSchema)]
568pub struct EditResponse {
569 /// Whether the edit succeeded (always `true` for non-`validate_only` tools).
570 pub success: bool,
571 /// SHA-256 hash of the file after the edit. `None` for `validate_only`.
572 #[serde(skip_serializing_if = "Option::is_none")]
573 pub new_version_hash: Option<String>,
574 /// Whether the code was reformatted (always `false` until LSP formatting is wired).
575 pub formatted: bool,
576 /// LSP validation result.
577 pub validation: EditValidation,
578 /// `true` when LSP validation was skipped (no language server available).
579 #[serde(default)]
580 pub validation_skipped: bool,
581 /// Machine-readable reason why validation was skipped.
582 #[serde(skip_serializing_if = "Option::is_none")]
583 pub validation_skipped_reason: Option<String>,
584}
585
586/// LSP validation result embedded in `EditResponse`.
587#[derive(Debug, Serialize, schemars::JsonSchema)]
588pub struct EditValidation {
589 /// `"passed"`, `"failed"`, or `"skipped"`.
590 pub status: String,
591 /// Errors introduced by the edit.
592 pub introduced_errors: Vec<pathfinder_common::error::DiagnosticError>,
593 /// Errors resolved by the edit.
594 pub resolved_errors: Vec<pathfinder_common::error::DiagnosticError>,
595}
596
597impl EditValidation {
598 /// Return a skipped validation result (no LSP available).
599 #[must_use]
600 pub fn skipped() -> Self {
601 Self {
602 status: "skipped".to_owned(),
603 introduced_errors: vec![],
604 resolved_errors: vec![],
605 }
606 }
607}
608
609/// The response for `create_file`.
610#[derive(Debug, Serialize, schemars::JsonSchema)]
611pub struct CreateFileResponse {
612 /// Whether the file creation succeeded.
613 pub success: bool,
614 /// Hash of the file version after creation.
615 pub version_hash: String,
616 /// Results of validation after file creation.
617 pub validation: ValidationResult,
618}
619
620/// The response for `delete_file`.
621#[derive(Debug, Serialize, schemars::JsonSchema)]
622pub struct DeleteFileResponse {
623 /// Whether the file deletion succeeded.
624 pub success: bool,
625}
626
627/// The metadata embedded in `structured_content` for `read_file`.
628#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
629pub struct ReadFileMetadata {
630 /// First line number returned from the file.
631 pub start_line: u32,
632 /// Number of lines returned.
633 pub lines_returned: u32,
634 /// Total number of lines in the file.
635 pub total_lines: u32,
636 /// Whether the output was truncated.
637 pub truncated: bool,
638 /// Version hash of the file.
639 pub version_hash: String,
640 /// Detected language of the file.
641 pub language: String,
642}
643
644/// The metadata embedded in `structured_content` for `write_file`.
645#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
646pub struct WriteFileMetadata {
647 /// Whether the write operation succeeded.
648 pub success: bool,
649 /// New version hash after writing.
650 pub new_version_hash: String,
651}
652
653/// Validation result for edits.
654#[derive(Debug, Serialize, schemars::JsonSchema)]
655pub struct ValidationResult {
656 /// Status of the validation (`"passed"`, `"failed"`, or `"skipped"`).
657 pub status: String,
658 /// Errors that were introduced in the edits.
659 pub introduced_errors: Vec<pathfinder_common::error::DiagnosticError>,
660}
661
662// ── Navigation Tool Response Types ─────────────────────────────────
663
664/// A dependency signature extracted for `read_with_deep_context`.
665#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
666pub struct DeepContextDependency {
667 /// Semantic path of the called symbol.
668 pub semantic_path: String,
669 /// Extracted signature (declaration line only, no body).
670 pub signature: String,
671 /// File path relative to workspace root.
672 pub file: String,
673 /// 1-indexed line of the definition.
674 pub line: usize,
675}
676
677/// The metadata embedded in `structured_content` for `read_with_deep_context`.
678#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
679pub struct ReadWithDeepContextMetadata {
680 /// Start line of the symbol (1-indexed).
681 pub start_line: usize,
682 /// End line of the symbol (1-indexed).
683 pub end_line: usize,
684 /// OCC version hash for the symbol's file.
685 pub version_hash: String,
686 /// Detected language.
687 pub language: String,
688 /// Signatures of all symbols called by this one.
689 #[serde(skip_serializing_if = "Vec::is_empty", default)]
690 pub dependencies: Vec<DeepContextDependency>,
691 /// `true` when LSP dependency resolution was unavailable (Tree-sitter only).
692 #[serde(default)]
693 pub degraded: bool,
694 /// Reason for degradation (e.g., `"no_lsp"`).
695 #[serde(skip_serializing_if = "Option::is_none")]
696 pub degraded_reason: Option<String>,
697}
698
699/// The response for `get_definition`.
700#[derive(Debug, Serialize, schemars::JsonSchema)]
701pub struct GetDefinitionResponse {
702 /// Relative file path of the definition site.
703 pub file: String,
704 /// 1-indexed line number of the definition.
705 pub line: u32,
706 /// 1-indexed column number.
707 pub column: u32,
708 /// First line of the definition (code preview).
709 pub preview: String,
710 /// `true` when LSP was unavailable and no fallback was possible.
711 #[serde(default)]
712 pub degraded: bool,
713 /// Reason for degradation.
714 #[serde(skip_serializing_if = "Option::is_none")]
715 pub degraded_reason: Option<String>,
716}
717
718/// A single reference in an impact analysis.
719#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
720pub struct ImpactReference {
721 /// Semantic path of the referencing/referenced symbol.
722 pub semantic_path: String,
723 /// File path relative to workspace root.
724 pub file: String,
725 /// 1-indexed line of the call site or definition.
726 pub line: usize,
727 /// A short code snippet showing the call site or declaration.
728 pub snippet: String,
729 /// OCC version hash for this file.
730 pub version_hash: String,
731 /// Direction of the reference relative to the target symbol.
732 ///
733 /// - `"incoming"` — this symbol calls or references the target (a caller).
734 /// - `"outgoing"` — the target calls or references this symbol (a callee).
735 /// - `"incoming_heuristic"` — inferred by grep fallback when LSP is unavailable;
736 /// treat as a candidate, not a confirmed call.
737 pub direction: String,
738 /// BFS traversal depth (0 = direct caller/callee, 1 = one hop away, etc.).
739 pub depth: usize,
740}
741
742/// The metadata embedded in `structured_content` for `analyze_impact`.
743#[derive(Debug, Serialize, serde::Deserialize, schemars::JsonSchema)]
744pub struct AnalyzeImpactMetadata {
745 /// Symbols that call the target (caller graph).
746 /// `null` when `degraded` is `true` — LSP was unavailable so callers are **unknown**.
747 /// An empty array `[]` means LSP confirmed zero callers.
748 pub incoming: Option<Vec<ImpactReference>>,
749 /// Symbols the target calls (callee graph).
750 /// `null` when `degraded` is `true` — LSP was unavailable so callees are **unknown**.
751 /// An empty array `[]` means LSP confirmed zero callees.
752 pub outgoing: Option<Vec<ImpactReference>>,
753 /// Number of transitive levels traversed.
754 pub depth_reached: u32,
755 /// Total files referenced across all incoming and outgoing references.
756 pub files_referenced: usize,
757 /// Whether the call hierarchy analysis was degraded (LSP unavailable or crashed).
758 /// Always present. When `true`, `incoming` and `outgoing` are `null` (not empty arrays).
759 pub degraded: bool,
760 /// Machine-readable reason for degradation (e.g., `no_lsp`, `lsp_crash`, `lsp_timeout`).
761 /// Absent when `degraded` is `false`.
762 #[serde(skip_serializing_if = "Option::is_none")]
763 pub degraded_reason: Option<String>,
764 /// SHA-256 version hashes for all referenced files (including the target file itself),
765 /// keyed by relative file path. Agents can use these as `base_version` for immediate
766 /// editing without a separate read call.
767 #[serde(skip_serializing_if = "std::collections::HashMap::is_empty", default)]
768 pub version_hashes: std::collections::HashMap<String, String>,
769}
770
771// ── Default Value Functions ─────────────────────────────────────────
772
773#[must_use]
774pub fn default_path_glob() -> String {
775 "**/*".to_owned()
776}
777#[must_use]
778pub const fn default_max_results() -> u32 {
779 50
780}
781#[must_use]
782pub const fn default_context_lines() -> u32 {
783 2
784}
785#[must_use]
786pub fn default_repo_map_path() -> String {
787 ".".to_owned()
788}
789#[must_use]
790pub const fn default_max_tokens() -> u32 {
791 16_000
792}
793#[must_use]
794pub const fn default_max_tokens_per_file() -> u32 {
795 2_000
796}
797#[must_use]
798pub const fn default_depth() -> u32 {
799 5
800}
801#[must_use]
802pub const fn default_max_depth() -> u32 {
803 2
804}
805#[must_use]
806pub const fn default_start_line() -> u32 {
807 1
808}
809#[must_use]
810pub const fn default_max_lines() -> u32 {
811 500
812}
813#[must_use]
814pub fn default_detail_level() -> String {
815 "compact".to_string()
816}