diff --git a/crates/kebab-core/src/fetch.rs b/crates/kebab-core/src/fetch.rs new file mode 100644 index 0000000..14374be --- /dev/null +++ b/crates/kebab-core/src/fetch.rs @@ -0,0 +1,87 @@ +//! p9-fb-35 verbatim fetch domain types. +//! +//! Three modes (chunk / doc / span) carried by [`FetchQuery`]; one +//! response shape ([`FetchResult`]) discriminated by [`FetchKind`]. +//! All types are `Serialize` so the CLI / MCP wire layers can hand +//! them straight through `serde_json::to_value`. + +use serde::{Deserialize, Serialize}; +use time::OffsetDateTime; + +use crate::asset::WorkspacePath; +use crate::chunk::Chunk; +use crate::ids::{ChunkId, DocumentId}; + +#[derive(Clone, Debug)] +pub enum FetchQuery { + Chunk(ChunkId), + Doc(DocumentId), + Span { + doc_id: DocumentId, + line_start: u32, + line_end: u32, + }, +} + +#[derive(Clone, Debug, Default)] +pub struct FetchOpts { + /// chunk mode only: ±N chunks. None = no surrounding context. + pub context: Option, + /// doc / span mode only: chars/4 budget. None = no cap. + pub max_tokens: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum FetchKind { + Chunk, + Doc, + Span, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct FetchResult { + pub kind: FetchKind, + pub doc_id: DocumentId, + pub doc_path: WorkspacePath, + #[serde(with = "time::serde::rfc3339")] + pub indexed_at: OffsetDateTime, + pub stale: bool, + // chunk mode payloads + #[serde(skip_serializing_if = "Option::is_none")] + pub chunk: Option, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub context_before: Vec, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub context_after: Vec, + // doc / span payloads + #[serde(skip_serializing_if = "Option::is_none")] + pub text: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub line_start: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub line_end: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub effective_end: Option, + pub truncated: bool, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fetch_opts_default_is_all_none() { + let o = FetchOpts::default(); + assert!(o.context.is_none()); + assert!(o.max_tokens.is_none()); + } + + #[test] + fn fetch_kind_serializes_snake_case() { + let v = serde_json::to_value(FetchKind::Chunk).unwrap(); + assert_eq!(v, serde_json::json!("chunk")); + let v = serde_json::to_value(FetchKind::Span).unwrap(); + assert_eq!(v, serde_json::json!("span")); + } +} diff --git a/crates/kebab-core/src/lib.rs b/crates/kebab-core/src/lib.rs index 6da7a53..7bbb01b 100644 --- a/crates/kebab-core/src/lib.rs +++ b/crates/kebab-core/src/lib.rs @@ -23,6 +23,7 @@ pub mod vector; pub mod errors; pub mod traits; pub mod normalize; +pub mod fetch; // Re-export the most commonly used items at the crate root, mirroring the // public surface listed in the task spec. @@ -68,3 +69,4 @@ pub use traits::{ SourceScope, TokenChunk, VectorStore, }; pub use normalize::{nfc, to_posix}; +pub use fetch::{FetchKind, FetchOpts, FetchQuery, FetchResult};