Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .config/bito.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# .bito.yaml — generated by building-in-the-open plugin
# Edit freely. Re-run plugin setup to regenerate from defaults.

dialect: en-us
max_grade: 12.0
passive_max_percent: 15.0
tokenizer: claude

rules:
- paths: [".handoffs/*.md"]
checks:
completeness:
template: handoff
tokens:
budget: 2000

- paths: ["record/decisions/*.md"]
checks:
completeness:
template: adr
readability:
max_grade: 12.0

- paths: ["record/designs/*.md"]
checks:
completeness:
template: design-doc
readability:
max_grade: 12.0

- paths: ["docs/**/*.md", "README.md"]
checks:
readability:
max_grade: 12.0
grammar:
passive_max: 15.0
8 changes: 8 additions & 0 deletions .config/scrat.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
type = "rust"

[commands]
test = "just test"

[ship]
no_publish = true
2 changes: 1 addition & 1 deletion .repo.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Changes here will be overwritten by Copier
_commit: v1.2.0
_commit: v1.3.0
_src_path: gh:claylo/claylo-rs
categories:
- command-line-utilities
Expand Down
60 changes: 52 additions & 8 deletions crates/bito-core/src/observability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ pub struct ObservabilityConfig {

impl ObservabilityConfig {
/// Create config from environment variables with optional overrides.
pub fn from_env_with_overrides(log_dir: Option<PathBuf>) -> Self {
pub fn from_env_with_overrides(service: &str, log_dir: Option<PathBuf>) -> Self {
Self {
service: env!("CARGO_PKG_NAME").to_string(),
service: service.to_string(),
log_dir,
}
}
Expand Down Expand Up @@ -193,9 +193,12 @@ where
event.record(&mut visitor);
map.extend(visitor.values);

let mut writer = self.writer.make_writer();
if serde_json::to_writer(&mut writer, &Value::Object(map)).is_ok() {
let _ = writer.write_all(b"\n");
// Buffer the entire line so it's written in a single write() syscall,
// which is atomic with O_APPEND for lines under PIPE_BUF (typically 4096).
if let Ok(mut buf) = serde_json::to_vec(&Value::Object(map)) {
buf.push(b'\n');
let mut writer = self.writer.make_writer();
let _ = writer.write_all(&buf);
}
}
}
Expand Down Expand Up @@ -354,9 +357,9 @@ fn resolve_log_target_with(
candidates.push(PathBuf::from(DEFAULT_LOG_DIR_UNIX));
}

// Use XDG-compliant data directory for log storage
if let Some(proj_dirs) = directories::ProjectDirs::from("", "", service) {
candidates.push(proj_dirs.data_local_dir().join("logs"));
// Platform-appropriate log directory
if let Some(log_dir) = platform_log_dir(service) {
candidates.push(log_dir);
}

if let Ok(dir) = std::env::current_dir() {
Expand Down Expand Up @@ -413,6 +416,26 @@ fn ensure_writable(dir: &Path, file_name: &str) -> Result<(), String> {
Ok(())
}

/// Resolve the platform-appropriate log directory for a service.
///
/// - macOS: `~/Library/Logs/{service}/`
/// - Linux/BSD: `$XDG_STATE_HOME/{service}/logs/` (default `~/.local/state/{service}/logs/`)
/// - Windows: `{LocalAppData}/{service}/logs/` (via `directories` crate)
fn platform_log_dir(service: &str) -> Option<PathBuf> {
if cfg!(target_os = "macos") {
std::env::var_os("HOME").map(|home| PathBuf::from(home).join("Library/Logs").join(service))
} else if cfg!(unix) {
let state_base = std::env::var_os("XDG_STATE_HOME")
.map(PathBuf::from)
.or_else(|| {
std::env::var_os("HOME").map(|home| PathBuf::from(home).join(".local/state"))
})?;
Some(state_base.join(service).join("logs"))
} else {
directories::ProjectDirs::from("", "", service).map(|p| p.data_local_dir().join("logs"))
}
}

// ============================================================================
// Tests
// ============================================================================
Expand Down Expand Up @@ -496,6 +519,27 @@ mod tests {
assert_eq!(&ts[10..11], "T", "date-time separator");
}

#[test]
fn platform_log_dir_contains_service_name() {
let dir = platform_log_dir("test-svc").expect("platform_log_dir should return Some");
let path = dir.to_str().expect("path should be valid UTF-8");
assert!(
path.contains("test-svc"),
"log dir should contain service name: {path}"
);
}

#[test]
#[cfg(target_os = "macos")]
fn platform_log_dir_uses_library_logs_on_macos() {
let dir = platform_log_dir("test-svc").unwrap();
let path = dir.to_str().unwrap();
assert!(
path.contains("Library/Logs"),
"macOS log dir should use ~/Library/Logs/: {path}"
);
}

#[test]
fn days_to_ymd_known_dates() {
// 1970-01-01 = day 0
Expand Down
1 change: 1 addition & 0 deletions crates/bito/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ async fn main() -> anyhow::Result<()> {
let (config, config_sources) = loader.load().context("failed to load configuration")?;

let obs_config = observability::ObservabilityConfig::from_env_with_overrides(
env!("CARGO_PKG_NAME"),
config
.log_dir
.as_ref()
Expand Down
7 changes: 6 additions & 1 deletion crates/bito/tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ use predicates::prelude::*;
/// cargo build directories, but works correctly for standard project layouts.
#[allow(deprecated)]
fn cmd() -> Command {
Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap()
let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
// Route log output to a temp directory so tests don't write to production paths
let prefix = env!("CARGO_PKG_NAME").to_uppercase().replace('-', "_");
let test_log_dir = std::env::temp_dir().join(format!("{}-test-logs", env!("CARGO_PKG_NAME")));
cmd.env(format!("{prefix}_LOG_DIR"), test_log_dir);
cmd
}

// =============================================================================
Expand Down
7 changes: 6 additions & 1 deletion crates/bito/tests/config_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ use tempfile::TempDir;
/// Returns a Command configured to run our binary.
#[allow(deprecated)]
fn cmd() -> Command {
Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap()
let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
// Route log output to a temp directory so tests don't write to production paths
let prefix = env!("CARGO_PKG_NAME").to_uppercase().replace('-', "_");
let test_log_dir = std::env::temp_dir().join(format!("{}-test-logs", env!("CARGO_PKG_NAME")));
cmd.env(format!("{prefix}_LOG_DIR"), test_log_dir);
cmd
}

/// Run `info --json` from a directory and parse the JSON output.
Expand Down