git_paw/mcp/query/mod.rs
1//! Data-layer reads for the MCP server.
2//!
3//! Every function here returns plain Rust / `serde_json` values built from
4//! deterministic sources: the broker's HTTP `/log`, files on disk, git
5//! process output, and parsed configuration. **No MCP types appear in this
6//! module** — that boundary (design D2) keeps the tool layer and any future
7//! transport reusable on top of the same reads.
8//!
9//! Degradation contract (design D4): functions return empty / `None` results
10//! when their data source is simply absent (no broker, no session, no
11//! governance config). They return `Err` only for genuine misconfiguration —
12//! a configured path that exists but cannot be read — so the tool layer can
13//! surface it to the client as a protocol error.
14
15pub mod conflicts;
16pub mod docs;
17pub mod git;
18pub mod governance;
19pub mod intents;
20pub mod learnings;
21pub mod session;
22pub mod source;
23pub mod specs;
24
25use std::path::{Path, PathBuf};
26use std::time::{SystemTime, UNIX_EPOCH};
27
28use crate::error::PawError;
29
30/// Current wall-clock time in seconds since the Unix epoch.
31#[must_use]
32pub fn now_unix() -> u64 {
33 SystemTime::now()
34 .duration_since(UNIX_EPOCH)
35 .map_or(0, |d| d.as_secs())
36}
37
38/// Resolves a possibly-relative configured path against the repository root.
39#[must_use]
40pub fn resolve_under_root(repo_root: &Path, path: &Path) -> PathBuf {
41 if path.is_absolute() {
42 path.to_path_buf()
43 } else {
44 repo_root.join(path)
45 }
46}
47
48/// Reads a configured document.
49///
50/// - `None` configured → `Ok(None)` (graceful degradation).
51/// - configured + readable → `Ok(Some(content))`.
52/// - configured + present-but-unreadable / missing → `Err` (misconfiguration
53/// the client should see).
54pub fn read_optional_doc(
55 repo_root: &Path,
56 configured: Option<&Path>,
57) -> Result<Option<String>, PawError> {
58 let Some(rel) = configured else {
59 return Ok(None);
60 };
61 let path = resolve_under_root(repo_root, rel);
62 std::fs::read_to_string(&path).map(Some).map_err(|e| {
63 PawError::McpError(format!(
64 "configured governance path {} could not be read: {e}",
65 path.display()
66 ))
67 })
68}