diff --git a/checkpoint.json b/checkpoint.json index 63f9b3d42..4c89b8c1f 100644 --- a/checkpoint.json +++ b/checkpoint.json @@ -1,19 +1,21 @@ { - "timestamp": "2026-03-16T05:24:10.116637Z", - "current_node": "simplify_opus", + "timestamp": "2026-03-16T05:31:33.426800Z", + "current_node": "simplify_gemini", "completed_nodes": [ "start", "toolchain", "preflight_compile", "preflight_lint", "implement", - "simplify_opus" + "simplify_opus", + "simplify_gemini" ], "node_retries": { "preflight_compile": 1, "preflight_lint": 1, "simplify_opus": 1, "implement": 1, + "simplify_gemini": 1, "toolchain": 1, "start": 1 }, @@ -23,13 +25,16 @@ "internal.retry_count.simplify_opus": 1, "thread.preflight_compile.current_node": "preflight_lint", "thread.implement.current_node": "simplify_opus", - "current_node": "simplify_opus", + "current_node": "simplify_gemini", + "response.simplify_gemini": "All clean. Build, tests, and clippy all pass. Here's a summary of what was fixed:\n\n## Summary\n\nThree fixes applied based on the review agents' findings:\n\n1. **Unified `send_to_segment` signature** (Quality, High) — Changed `send_to_segment` from taking `&Track` to taking `&Path`, matching `send_panic_to_sentry`'s signature. This moved the file-reading logic into the function itself, making both sender functions consistent and simplifying the `SendAnalytics` handler in `main.rs` from a 6-line async block to a single function call.\n\n2. **Removed redundant `release` in `ClientOptions`** (Quality, Low) — The Sentry event already carries `release` from `build_panic_event()`. Setting it again in `ClientOptions` was redundant (Sentry uses the event-level value when present). Simplified to `sentry::ClientOptions::default()`.\n\n3. **Used `crate::version::FABRO_VERSION`** (Reuse, Low) — Replaced raw `env!(\"CARGO_PKG_VERSION\")` in `panic.rs` with the existing `crate::version::FABRO_VERSION` constant for consistency with the rest of the codebase.\n\n**Skipped findings (not worth addressing):**\n- `panic_message()` duplication with `engine.rs` — Different crates, different prefixes, only 5 lines. Not worth cross-crate extraction.\n- `dirs::home_dir().join(\".fabro\")` repeated 15+ times — Valid but out of scope for this diff.\n- Stringly-typed subcommand names — clap requires string literals in attributes, so constants can only help 2 of 4 sites. Marginal benefit.\n- All efficiency findings — No issues found; code is well-structured for its fire-and-forget telemetry purpose.", "graph.model_stylesheet": "\n * { backend: api; model: claude-opus-4-6;}\n ", + "thread.simplify_opus.current_node": "simplify_gemini", + "internal.retry_count.simplify_gemini": 1, "thread.toolchain.current_node": "preflight_compile", - "last_stage": "simplify_opus", + "last_stage": "simplify_gemini", "graph.goal": "# Add Sentry panic reporting to fabro CLI\n\n## Context\n\nFabro has no panic reporting. When the CLI panics, we lose visibility. We want Sentry-based panic reporting modeled on qlty's approach: serialize a Sentry event to a temp file, double-fork a detached subprocess to upload it.\n\nThe existing `__send_analytics` sender uses a simple `Command::spawn()` which is unreliable — the child can get killed when the parent exits or the terminal session ends. We'll fix both senders to use the double-fork pattern (fork → setsid → close_fd → fork → exec) from qlty, which fully detaches the subprocess.\n\n## Files to modify\n\n### 1. `Cargo.toml` (workspace root) — add deps\n```toml\nsentry = { version = \"0.35\", default-features = false, features = [\"backtrace\", \"contexts\", \"ureq\", \"rustls\"] }\nfork = \"0.2\"\nexec = \"0.3\"\n```\n\n### 2. `lib/crates/fabro-util/Cargo.toml` — add deps\n```toml\nsentry.workspace = true\nfork.workspace = true\nexec.workspace = true\n```\n\n### 3. `lib/crates/fabro-util/src/telemetry/mod.rs`\n- Add `pub mod panic;`\n- Add `pub mod spawn;`\n\n### 4. `lib/crates/fabro-util/src/telemetry/spawn.rs` — NEW FILE\n\nExtract a shared `spawn_detached(args: &[&str], env: &[(&str, &str)])` function used by both analytics sender and panic sender. Implements:\n- **Unix**: double-fork via `fork` crate (fork → setsid → close_fd → fork → exec)\n- **Windows**: `Command::new().creation_flags(DETACHED_PROCESS).spawn()`\n\n### 5. `lib/crates/fabro-util/src/telemetry/sender.rs` — refactor to use `spawn_detached`\n\nReplace the `Command::new().spawn()` call with `spawn::spawn_detached()`.\n\n### 6. `lib/crates/fabro-util/src/telemetry/panic.rs` — NEW FILE\n\nCore logic:\n\n- `install_panic_hook()` — chains onto default hook (like qlty). Calls `report_panic()` then delegates to default.\n- `report_panic(info: &PanicHookInfo)` — checks telemetry level (Off → return), filters \"Broken pipe\" panics, builds `sentry::protocol::Event` with:\n - Exception with `mechanism.ty = \"panic\"`, `handled = false`\n - Panic message as `value`\n - `sentry_backtrace::current_stacktrace()`\n - OS context\n - Level = Fatal\n- `spawn_panic_sender(event)` — serialize event to `~/.fabro/tmp/fabro-panic-{id}.json`, double-fork `fabro __send_panic ` with `FABRO_TELEMETRY=off`.\n- `pub async fn send_panic_to_sentry(path: &Path)` — read JSON, init sentry client with `SENTRY_DSN` (compile-time `option_env!`), `sentry::capture_event()`, delete temp file.\n\n### 7. `lib/crates/fabro-cli/src/main.rs` — two changes\n\n**a) Install panic hook early in `main()`** (before `main_inner()`):\n```rust\nfabro_util::telemetry::panic::install_panic_hook();\n```\n\n**b) Add `__send_panic` hidden subcommand** (same pattern as `__send_analytics`).\n\n## Key design decisions\n\n- **Double-fork for all background senders** — ensures subprocess survives parent exit and terminal close. Fixes existing `__send_analytics` reliability too.\n- **Shared `spawn_detached`** — avoids duplicating the fork logic between analytics and panic senders.\n- **Telemetry level respected** — `Off` skips panic reporting. `Errors` and `All` both report.\n- **Recursion prevention** — subprocess sets `FABRO_TELEMETRY=off`.\n- **Compile-time DSN** — `option_env!(\"SENTRY_DSN\")`, no-ops when unset (dev builds).\n- **Filter broken pipe** — CLI tools get SIGPIPE from `| head` etc., not a real bug.\n\n## Implementation order (red/green TDD)\n\nWork bottom-up through the dependency chain. For each step, write the test first (red), then write the minimal code to make it pass (green).\n\n### Step 1: `spawn_detached` (fabro-util)\n- **Red**: Test that `spawn_detached` constructs the right args (unit-testable parts: temp file writing, arg assembly). On Unix, test the double-fork integration by spawning a real process that writes a marker file.\n- **Green**: Implement `spawn.rs` with fork/setsid/close_fd/fork/exec on Unix, DETACHED_PROCESS on Windows.\n\n### Step 2: Refactor `sender.rs` to use `spawn_detached`\n- **Red**: Existing `send_noops_without_write_key` test still passes. Add test verifying temp file is written with correct JSON.\n- **Green**: Replace `Command::spawn()` with `spawn_detached()`.\n\n### Step 3: `panic.rs` — event building\n- **Red**: Test `build_panic_event()` produces correct Sentry event structure (exception type, mechanism, level, stacktrace present).\n- **Green**: Implement event builder.\n\n### Step 4: `panic.rs` — hook + spawn\n- **Red**: Test that `report_panic` filters broken pipe. Test that it no-ops when telemetry is Off.\n- **Green**: Implement `install_panic_hook()`, `report_panic()`, `spawn_panic_sender()`.\n\n### Step 5: `panic.rs` — Sentry sender\n- **Red**: Test `send_panic_to_sentry` no-ops without DSN (like analytics no-ops without write key).\n- **Green**: Implement the `__send_panic` worker function.\n\n### Step 6: Wire up in `main.rs`\n- Add `__send_panic` subcommand + install panic hook.\n- Verify: `cargo build --workspace && cargo test --workspace && cargo clippy --workspace -- -D warnings`\n", "response.implement": "All tests pass across the entire workspace — zero failures. Here's a summary of all the changes made:\n\n## Summary of Changes\n\n### 1. `Cargo.toml` (workspace root) — Added dependencies\n- `sentry = { version = \"0.35\", default-features = false, features = [\"backtrace\", \"contexts\", \"ureq\", \"rustls\"] }`\n- `fork = \"0.2\"`\n- `exec = \"0.3\"`\n\n### 2. `lib/crates/fabro-util/Cargo.toml` — Added dependencies\n- `sentry.workspace = true`\n- `fork.workspace = true`\n- `exec.workspace = true`\n\n### 3. `lib/crates/fabro-util/src/telemetry/mod.rs` — Registered new modules\n- Added `pub mod panic;`\n- Added `pub mod spawn;`\n\n### 4. `lib/crates/fabro-util/src/telemetry/spawn.rs` — **NEW FILE**\nShared `spawn_detached()` function with:\n- **Unix**: double-fork pattern (`fork → setsid → close_fd → fork → exec`) via the `fork` and `exec` crates\n- **Windows**: `Command::new().creation_flags(DETACHED_PROCESS).spawn()`\n- Helper functions: `write_temp_json()`, `build_fabro_argv()`, `current_exe_str()`, `path_exists()`\n- **5 tests**: temp file creation, argv building, current exe, empty args no-op, Unix integration test with marker file\n\n### 5. `lib/crates/fabro-util/src/telemetry/sender.rs` — Refactored to use `spawn_detached`\n- Replaced `Command::new().spawn()` with `super::spawn::spawn_detached()` and `super::spawn::write_temp_json()`\n- Simplified `spawn_sender()` to return `()` (no longer `Result`)\n- Existing `send_noops_without_write_key` test continues to pass\n\n### 6. `lib/crates/fabro-util/src/telemetry/panic.rs` — **NEW FILE**\nCore panic reporting logic:\n- `install_panic_hook()` — chains onto default hook, calls `report_panic()` first\n- `build_panic_event()` — builds `sentry::protocol::Event` with exception (type=`\"panic\"`, mechanism `handled=false`), stacktrace, OS context, Fatal level, release version\n- `report_panic()` — checks telemetry level (Off → skip), filters \"Broken pipe\" panics, builds event, spawns sender\n- `spawn_panic_sender()` — serializes event to `~/.fabro/tmp/fabro-panic-{id}.json`, double-forks `fabro __send_panic ` with `FABRO_TELEMETRY=off`\n- `send_panic_to_sentry()` — reads JSON, inits Sentry client with compile-time `SENTRY_DSN`, captures event, flushes\n- **5 tests**: event structure validation, broken pipe filtering, telemetry-off no-op, DSN-missing no-op, JSON round-trip\n\n### 7. `lib/crates/fabro-cli/src/main.rs` — Wired up\n- `install_panic_hook()` called at the very start of `main()` (before `main_inner()`)\n- Added `SendPanic` hidden subcommand variant (mirrors `SendAnalytics`)\n- Added command name mapping `\"__send_panic\"`\n- Added handler that reads the JSON, calls `send_panic_to_sentry()`, and cleans up the temp file", - "internal.thread_id": "implement", + "internal.thread_id": "simplify_opus", "graph.rankdir": "LR", "internal.fidelity": "compact", "internal.retry_count.start": 1, @@ -39,13 +44,13 @@ "failure_class": "", "internal.retry_count.preflight_lint": 1, "thread.preflight_lint.current_node": "implement", - "last_response": "Clean. Here's a summary of what was fixed:\n\n## Summary of Changes\n\n1. **Removed dead code** (`spawn.rs`):\n - Deleted `build_fabro_argv()` — defined but never called; both callers constructed argv ", + "last_response": "All clean. Build, tests, and clippy all pass. Here's a summary of what was fixed:\n\n## Summary\n\nThree fixes applied based on the review agents' findings:\n\n1. **Unified `send_to_segment` signature** (Qu", "internal.retry_count.toolchain": 1, "internal.node_visit_count": 1, "response.simplify_opus": "Clean. Here's a summary of what was fixed:\n\n## Summary of Changes\n\n1. **Removed dead code** (`spawn.rs`):\n - Deleted `build_fabro_argv()` — defined but never called; both callers constructed argv inline\n - Deleted `path_exists()` — trivial `Path::exists()` wrapper, never used outside the module\n - Deleted `current_exe_str()` — absorbed into the new shared helper\n - Deleted `write_temp_json()` — absorbed into the new shared helper\n - Removed unused `use std::path::Path` import\n - Removed 3 tests for the deleted functions\n\n2. **Eliminated copy-paste** (`spawn.rs`, `sender.rs`, `panic.rs`):\n - Extracted `spawn_fabro_subcommand()` in `spawn.rs` — a single function that handles the repeated pattern of: serialize JSON → write temp file → resolve current exe → spawn detached with `FABRO_TELEMETRY=off`\n - Simplified `spawn_sender()` in `sender.rs` from 20 lines to 6\n - Simplified `spawn_panic_sender()` in `panic.rs` from 20 lines to 6\n\n3. **Fixed dead `eprintln!`** (`spawn.rs`):\n - After `fork::close_fd()` closes stderr, the `eprintln!` for exec failure was a no-op. Changed to `let _err =` since nothing can be done after fd close.\n\n4. **Restored missing trailing newlines** in `Cargo.toml` and `lib/crates/fabro-util/Cargo.toml`.", "outcome": "success", "thread.start.current_node": "toolchain", - "current.preamble": "Goal: # Add Sentry panic reporting to fabro CLI\n\n## Context\n\nFabro has no panic reporting. When the CLI panics, we lose visibility. We want Sentry-based panic reporting modeled on qlty's approach: serialize a Sentry event to a temp file, double-fork a detached subprocess to upload it.\n\nThe existing `__send_analytics` sender uses a simple `Command::spawn()` which is unreliable — the child can get killed when the parent exits or the terminal session ends. We'll fix both senders to use the double-fork pattern (fork → setsid → close_fd → fork → exec) from qlty, which fully detaches the subprocess.\n\n## Files to modify\n\n### 1. `Cargo.toml` (workspace root) — add deps\n```toml\nsentry = { version = \"0.35\", default-features = false, features = [\"backtrace\", \"contexts\", \"ureq\", \"rustls\"] }\nfork = \"0.2\"\nexec = \"0.3\"\n```\n\n### 2. `lib/crates/fabro-util/Cargo.toml` — add deps\n```toml\nsentry.workspace = true\nfork.workspace = true\nexec.workspace = true\n```\n\n### 3. `lib/crates/fabro-util/src/telemetry/mod.rs`\n- Add `pub mod panic;`\n- Add `pub mod spawn;`\n\n### 4. `lib/crates/fabro-util/src/telemetry/spawn.rs` — NEW FILE\n\nExtract a shared `spawn_detached(args: &[&str], env: &[(&str, &str)])` function used by both analytics sender and panic sender. Implements:\n- **Unix**: double-fork via `fork` crate (fork → setsid → close_fd → fork → exec)\n- **Windows**: `Command::new().creation_flags(DETACHED_PROCESS).spawn()`\n\n### 5. `lib/crates/fabro-util/src/telemetry/sender.rs` — refactor to use `spawn_detached`\n\nReplace the `Command::new().spawn()` call with `spawn::spawn_detached()`.\n\n### 6. `lib/crates/fabro-util/src/telemetry/panic.rs` — NEW FILE\n\nCore logic:\n\n- `install_panic_hook()` — chains onto default hook (like qlty). Calls `report_panic()` then delegates to default.\n- `report_panic(info: &PanicHookInfo)` — checks telemetry level (Off → return), filters \"Broken pipe\" panics, builds `sentry::protocol::Event` with:\n - Exception with `mechanism.ty = \"panic\"`, `handled = false`\n - Panic message as `value`\n - `sentry_backtrace::current_stacktrace()`\n - OS context\n - Level = Fatal\n- `spawn_panic_sender(event)` — serialize event to `~/.fabro/tmp/fabro-panic-{id}.json`, double-fork `fabro __send_panic ` with `FABRO_TELEMETRY=off`.\n- `pub async fn send_panic_to_sentry(path: &Path)` — read JSON, init sentry client with `SENTRY_DSN` (compile-time `option_env!`), `sentry::capture_event()`, delete temp file.\n\n### 7. `lib/crates/fabro-cli/src/main.rs` — two changes\n\n**a) Install panic hook early in `main()`** (before `main_inner()`):\n```rust\nfabro_util::telemetry::panic::install_panic_hook();\n```\n\n**b) Add `__send_panic` hidden subcommand** (same pattern as `__send_analytics`).\n\n## Key design decisions\n\n- **Double-fork for all background senders** — ensures subprocess survives parent exit and terminal close. Fixes existing `__send_analytics` reliability too.\n- **Shared `spawn_detached`** — avoids duplicating the fork logic between analytics and panic senders.\n- **Telemetry level respected** — `Off` skips panic reporting. `Errors` and `All` both report.\n- **Recursion prevention** — subprocess sets `FABRO_TELEMETRY=off`.\n- **Compile-time DSN** — `option_env!(\"SENTRY_DSN\")`, no-ops when unset (dev builds).\n- **Filter broken pipe** — CLI tools get SIGPIPE from `| head` etc., not a real bug.\n\n## Implementation order (red/green TDD)\n\nWork bottom-up through the dependency chain. For each step, write the test first (red), then write the minimal code to make it pass (green).\n\n### Step 1: `spawn_detached` (fabro-util)\n- **Red**: Test that `spawn_detached` constructs the right args (unit-testable parts: temp file writing, arg assembly). On Unix, test the double-fork integration by spawning a real process that writes a marker file.\n- **Green**: Implement `spawn.rs` with fork/setsid/close_fd/fork/exec on Unix, DETACHED_PROCESS on Windows.\n\n### Step 2: Refactor `sender.rs` to use `spawn_detached`\n- **Red**: Existing `send_noops_without_write_key` test still passes. Add test verifying temp file is written with correct JSON.\n- **Green**: Replace `Command::spawn()` with `spawn_detached()`.\n\n### Step 3: `panic.rs` — event building\n- **Red**: Test `build_panic_event()` produces correct Sentry event structure (exception type, mechanism, level, stacktrace present).\n- **Green**: Implement event builder.\n\n### Step 4: `panic.rs` — hook + spawn\n- **Red**: Test that `report_panic` filters broken pipe. Test that it no-ops when telemetry is Off.\n- **Green**: Implement `install_panic_hook()`, `report_panic()`, `spawn_panic_sender()`.\n\n### Step 5: `panic.rs` — Sentry sender\n- **Red**: Test `send_panic_to_sentry` no-ops without DSN (like analytics no-ops without write key).\n- **Green**: Implement the `__send_panic` worker function.\n\n### Step 6: Wire up in `main.rs`\n- Add `__send_panic` subcommand + install panic hook.\n- Verify: `cargo build --workspace && cargo test --workspace && cargo clippy --workspace -- -D warnings`\n\n\n## Completed stages\n- **toolchain**: success\n - Script: `command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1`\n - Stdout:\n ```\n cargo 1.94.0 (85eff7c80 2026-01-15)\n ```\n - Stderr: (empty)\n- **preflight_compile**: success\n - Script: `cargo check -q --workspace 2>&1`\n - Stdout: (empty)\n - Stderr: (empty)\n- **preflight_lint**: success\n - Script: `cargo clippy -q --workspace -- -D warnings 2>&1`\n - Stdout: (empty)\n - Stderr: (empty)\n- **implement**: success\n - Model: claude-opus-4-6, 89.1k tokens in / 13.5k out\n - Files: /home/daytona/workspace/lib/crates/fabro-cli/src/main.rs, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/mod.rs, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/panic.rs, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/sender.rs, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/spawn.rs\n", + "current.preamble": "Goal: # Add Sentry panic reporting to fabro CLI\n\n## Context\n\nFabro has no panic reporting. When the CLI panics, we lose visibility. We want Sentry-based panic reporting modeled on qlty's approach: serialize a Sentry event to a temp file, double-fork a detached subprocess to upload it.\n\nThe existing `__send_analytics` sender uses a simple `Command::spawn()` which is unreliable — the child can get killed when the parent exits or the terminal session ends. We'll fix both senders to use the double-fork pattern (fork → setsid → close_fd → fork → exec) from qlty, which fully detaches the subprocess.\n\n## Files to modify\n\n### 1. `Cargo.toml` (workspace root) — add deps\n```toml\nsentry = { version = \"0.35\", default-features = false, features = [\"backtrace\", \"contexts\", \"ureq\", \"rustls\"] }\nfork = \"0.2\"\nexec = \"0.3\"\n```\n\n### 2. `lib/crates/fabro-util/Cargo.toml` — add deps\n```toml\nsentry.workspace = true\nfork.workspace = true\nexec.workspace = true\n```\n\n### 3. `lib/crates/fabro-util/src/telemetry/mod.rs`\n- Add `pub mod panic;`\n- Add `pub mod spawn;`\n\n### 4. `lib/crates/fabro-util/src/telemetry/spawn.rs` — NEW FILE\n\nExtract a shared `spawn_detached(args: &[&str], env: &[(&str, &str)])` function used by both analytics sender and panic sender. Implements:\n- **Unix**: double-fork via `fork` crate (fork → setsid → close_fd → fork → exec)\n- **Windows**: `Command::new().creation_flags(DETACHED_PROCESS).spawn()`\n\n### 5. `lib/crates/fabro-util/src/telemetry/sender.rs` — refactor to use `spawn_detached`\n\nReplace the `Command::new().spawn()` call with `spawn::spawn_detached()`.\n\n### 6. `lib/crates/fabro-util/src/telemetry/panic.rs` — NEW FILE\n\nCore logic:\n\n- `install_panic_hook()` — chains onto default hook (like qlty). Calls `report_panic()` then delegates to default.\n- `report_panic(info: &PanicHookInfo)` — checks telemetry level (Off → return), filters \"Broken pipe\" panics, builds `sentry::protocol::Event` with:\n - Exception with `mechanism.ty = \"panic\"`, `handled = false`\n - Panic message as `value`\n - `sentry_backtrace::current_stacktrace()`\n - OS context\n - Level = Fatal\n- `spawn_panic_sender(event)` — serialize event to `~/.fabro/tmp/fabro-panic-{id}.json`, double-fork `fabro __send_panic ` with `FABRO_TELEMETRY=off`.\n- `pub async fn send_panic_to_sentry(path: &Path)` — read JSON, init sentry client with `SENTRY_DSN` (compile-time `option_env!`), `sentry::capture_event()`, delete temp file.\n\n### 7. `lib/crates/fabro-cli/src/main.rs` — two changes\n\n**a) Install panic hook early in `main()`** (before `main_inner()`):\n```rust\nfabro_util::telemetry::panic::install_panic_hook();\n```\n\n**b) Add `__send_panic` hidden subcommand** (same pattern as `__send_analytics`).\n\n## Key design decisions\n\n- **Double-fork for all background senders** — ensures subprocess survives parent exit and terminal close. Fixes existing `__send_analytics` reliability too.\n- **Shared `spawn_detached`** — avoids duplicating the fork logic between analytics and panic senders.\n- **Telemetry level respected** — `Off` skips panic reporting. `Errors` and `All` both report.\n- **Recursion prevention** — subprocess sets `FABRO_TELEMETRY=off`.\n- **Compile-time DSN** — `option_env!(\"SENTRY_DSN\")`, no-ops when unset (dev builds).\n- **Filter broken pipe** — CLI tools get SIGPIPE from `| head` etc., not a real bug.\n\n## Implementation order (red/green TDD)\n\nWork bottom-up through the dependency chain. For each step, write the test first (red), then write the minimal code to make it pass (green).\n\n### Step 1: `spawn_detached` (fabro-util)\n- **Red**: Test that `spawn_detached` constructs the right args (unit-testable parts: temp file writing, arg assembly). On Unix, test the double-fork integration by spawning a real process that writes a marker file.\n- **Green**: Implement `spawn.rs` with fork/setsid/close_fd/fork/exec on Unix, DETACHED_PROCESS on Windows.\n\n### Step 2: Refactor `sender.rs` to use `spawn_detached`\n- **Red**: Existing `send_noops_without_write_key` test still passes. Add test verifying temp file is written with correct JSON.\n- **Green**: Replace `Command::spawn()` with `spawn_detached()`.\n\n### Step 3: `panic.rs` — event building\n- **Red**: Test `build_panic_event()` produces correct Sentry event structure (exception type, mechanism, level, stacktrace present).\n- **Green**: Implement event builder.\n\n### Step 4: `panic.rs` — hook + spawn\n- **Red**: Test that `report_panic` filters broken pipe. Test that it no-ops when telemetry is Off.\n- **Green**: Implement `install_panic_hook()`, `report_panic()`, `spawn_panic_sender()`.\n\n### Step 5: `panic.rs` — Sentry sender\n- **Red**: Test `send_panic_to_sentry` no-ops without DSN (like analytics no-ops without write key).\n- **Green**: Implement the `__send_panic` worker function.\n\n### Step 6: Wire up in `main.rs`\n- Add `__send_panic` subcommand + install panic hook.\n- Verify: `cargo build --workspace && cargo test --workspace && cargo clippy --workspace -- -D warnings`\n\n\n## Completed stages\n- **toolchain**: success\n - Script: `command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1`\n - Stdout:\n ```\n cargo 1.94.0 (85eff7c80 2026-01-15)\n ```\n - Stderr: (empty)\n- **preflight_compile**: success\n - Script: `cargo check -q --workspace 2>&1`\n - Stdout: (empty)\n - Stderr: (empty)\n- **preflight_lint**: success\n - Script: `cargo clippy -q --workspace -- -D warnings 2>&1`\n - Stdout: (empty)\n - Stderr: (empty)\n- **implement**: success\n - Model: claude-opus-4-6, 89.1k tokens in / 13.5k out\n - Files: /home/daytona/workspace/lib/crates/fabro-cli/src/main.rs, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/mod.rs, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/panic.rs, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/sender.rs, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/spawn.rs\n- **simplify_opus**: success\n - Model: claude-opus-4-6, 60.2k tokens in / 17.7k out\n - Files: /home/daytona/workspace/Cargo.toml, /home/daytona/workspace/lib/crates/fabro-util/Cargo.toml, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/panic.rs, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/sender.rs, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/spawn.rs\n", "command.stderr": "" }, "logs": [], @@ -80,6 +85,30 @@ ], "duration_ms": 518160 }, + "simplify_gemini": { + "status": "success", + "context_updates": { + "response.simplify_gemini": "All clean. Build, tests, and clippy all pass. Here's a summary of what was fixed:\n\n## Summary\n\nThree fixes applied based on the review agents' findings:\n\n1. **Unified `send_to_segment` signature** (Quality, High) — Changed `send_to_segment` from taking `&Track` to taking `&Path`, matching `send_panic_to_sentry`'s signature. This moved the file-reading logic into the function itself, making both sender functions consistent and simplifying the `SendAnalytics` handler in `main.rs` from a 6-line async block to a single function call.\n\n2. **Removed redundant `release` in `ClientOptions`** (Quality, Low) — The Sentry event already carries `release` from `build_panic_event()`. Setting it again in `ClientOptions` was redundant (Sentry uses the event-level value when present). Simplified to `sentry::ClientOptions::default()`.\n\n3. **Used `crate::version::FABRO_VERSION`** (Reuse, Low) — Replaced raw `env!(\"CARGO_PKG_VERSION\")` in `panic.rs` with the existing `crate::version::FABRO_VERSION` constant for consistency with the rest of the codebase.\n\n**Skipped findings (not worth addressing):**\n- `panic_message()` duplication with `engine.rs` — Different crates, different prefixes, only 5 lines. Not worth cross-crate extraction.\n- `dirs::home_dir().join(\".fabro\")` repeated 15+ times — Valid but out of scope for this diff.\n- Stringly-typed subcommand names — clap requires string literals in attributes, so constants can only help 2 of 4 sites. Marginal benefit.\n- All efficiency findings — No issues found; code is well-structured for its fire-and-forget telemetry purpose.", + "last_stage": "simplify_gemini", + "last_response": "All clean. Build, tests, and clippy all pass. Here's a summary of what was fixed:\n\n## Summary\n\nThree fixes applied based on the review agents' findings:\n\n1. **Unified `send_to_segment` signature** (Qu" + }, + "notes": "Stage completed: simplify_gemini", + "usage": { + "model": "claude-opus-4-6", + "input_tokens": 54350, + "output_tokens": 12412, + "cache_read_tokens": 511814, + "cache_write_tokens": 58461, + "reasoning_tokens": 513, + "cost": 1.74615 + }, + "files_touched": [ + "/home/daytona/workspace/lib/crates/fabro-cli/src/main.rs", + "/home/daytona/workspace/lib/crates/fabro-util/src/telemetry/panic.rs", + "/home/daytona/workspace/lib/crates/fabro-util/src/telemetry/sender.rs" + ], + "duration_ms": 441281 + }, "simplify_opus": { "status": "success", "context_updates": { @@ -134,8 +163,9 @@ "duration_ms": 72559 } }, - "next_node_id": "simplify_gemini", + "next_node_id": "simplify_gpt", "node_visits": { + "simplify_gemini": 1, "preflight_compile": 1, "toolchain": 1, "preflight_lint": 1, diff --git a/nodes/simplify_gemini/prompt.md b/nodes/simplify_gemini/prompt.md new file mode 100644 index 000000000..5080a1227 --- /dev/null +++ b/nodes/simplify_gemini/prompt.md @@ -0,0 +1,173 @@ +Goal: # Add Sentry panic reporting to fabro CLI + +## Context + +Fabro has no panic reporting. When the CLI panics, we lose visibility. We want Sentry-based panic reporting modeled on qlty's approach: serialize a Sentry event to a temp file, double-fork a detached subprocess to upload it. + +The existing `__send_analytics` sender uses a simple `Command::spawn()` which is unreliable — the child can get killed when the parent exits or the terminal session ends. We'll fix both senders to use the double-fork pattern (fork → setsid → close_fd → fork → exec) from qlty, which fully detaches the subprocess. + +## Files to modify + +### 1. `Cargo.toml` (workspace root) — add deps +```toml +sentry = { version = "0.35", default-features = false, features = ["backtrace", "contexts", "ureq", "rustls"] } +fork = "0.2" +exec = "0.3" +``` + +### 2. `lib/crates/fabro-util/Cargo.toml` — add deps +```toml +sentry.workspace = true +fork.workspace = true +exec.workspace = true +``` + +### 3. `lib/crates/fabro-util/src/telemetry/mod.rs` +- Add `pub mod panic;` +- Add `pub mod spawn;` + +### 4. `lib/crates/fabro-util/src/telemetry/spawn.rs` — NEW FILE + +Extract a shared `spawn_detached(args: &[&str], env: &[(&str, &str)])` function used by both analytics sender and panic sender. Implements: +- **Unix**: double-fork via `fork` crate (fork → setsid → close_fd → fork → exec) +- **Windows**: `Command::new().creation_flags(DETACHED_PROCESS).spawn()` + +### 5. `lib/crates/fabro-util/src/telemetry/sender.rs` — refactor to use `spawn_detached` + +Replace the `Command::new().spawn()` call with `spawn::spawn_detached()`. + +### 6. `lib/crates/fabro-util/src/telemetry/panic.rs` — NEW FILE + +Core logic: + +- `install_panic_hook()` — chains onto default hook (like qlty). Calls `report_panic()` then delegates to default. +- `report_panic(info: &PanicHookInfo)` — checks telemetry level (Off → return), filters "Broken pipe" panics, builds `sentry::protocol::Event` with: + - Exception with `mechanism.ty = "panic"`, `handled = false` + - Panic message as `value` + - `sentry_backtrace::current_stacktrace()` + - OS context + - Level = Fatal +- `spawn_panic_sender(event)` — serialize event to `~/.fabro/tmp/fabro-panic-{id}.json`, double-fork `fabro __send_panic ` with `FABRO_TELEMETRY=off`. +- `pub async fn send_panic_to_sentry(path: &Path)` — read JSON, init sentry client with `SENTRY_DSN` (compile-time `option_env!`), `sentry::capture_event()`, delete temp file. + +### 7. `lib/crates/fabro-cli/src/main.rs` — two changes + +**a) Install panic hook early in `main()`** (before `main_inner()`): +```rust +fabro_util::telemetry::panic::install_panic_hook(); +``` + +**b) Add `__send_panic` hidden subcommand** (same pattern as `__send_analytics`). + +## Key design decisions + +- **Double-fork for all background senders** — ensures subprocess survives parent exit and terminal close. Fixes existing `__send_analytics` reliability too. +- **Shared `spawn_detached`** — avoids duplicating the fork logic between analytics and panic senders. +- **Telemetry level respected** — `Off` skips panic reporting. `Errors` and `All` both report. +- **Recursion prevention** — subprocess sets `FABRO_TELEMETRY=off`. +- **Compile-time DSN** — `option_env!("SENTRY_DSN")`, no-ops when unset (dev builds). +- **Filter broken pipe** — CLI tools get SIGPIPE from `| head` etc., not a real bug. + +## Implementation order (red/green TDD) + +Work bottom-up through the dependency chain. For each step, write the test first (red), then write the minimal code to make it pass (green). + +### Step 1: `spawn_detached` (fabro-util) +- **Red**: Test that `spawn_detached` constructs the right args (unit-testable parts: temp file writing, arg assembly). On Unix, test the double-fork integration by spawning a real process that writes a marker file. +- **Green**: Implement `spawn.rs` with fork/setsid/close_fd/fork/exec on Unix, DETACHED_PROCESS on Windows. + +### Step 2: Refactor `sender.rs` to use `spawn_detached` +- **Red**: Existing `send_noops_without_write_key` test still passes. Add test verifying temp file is written with correct JSON. +- **Green**: Replace `Command::spawn()` with `spawn_detached()`. + +### Step 3: `panic.rs` — event building +- **Red**: Test `build_panic_event()` produces correct Sentry event structure (exception type, mechanism, level, stacktrace present). +- **Green**: Implement event builder. + +### Step 4: `panic.rs` — hook + spawn +- **Red**: Test that `report_panic` filters broken pipe. Test that it no-ops when telemetry is Off. +- **Green**: Implement `install_panic_hook()`, `report_panic()`, `spawn_panic_sender()`. + +### Step 5: `panic.rs` — Sentry sender +- **Red**: Test `send_panic_to_sentry` no-ops without DSN (like analytics no-ops without write key). +- **Green**: Implement the `__send_panic` worker function. + +### Step 6: Wire up in `main.rs` +- Add `__send_panic` subcommand + install panic hook. +- Verify: `cargo build --workspace && cargo test --workspace && cargo clippy --workspace -- -D warnings` + + +## Completed stages +- **toolchain**: success + - Script: `command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1` + - Stdout: + ``` + cargo 1.94.0 (85eff7c80 2026-01-15) + ``` + - Stderr: (empty) +- **preflight_compile**: success + - Script: `cargo check -q --workspace 2>&1` + - Stdout: (empty) + - Stderr: (empty) +- **preflight_lint**: success + - Script: `cargo clippy -q --workspace -- -D warnings 2>&1` + - Stdout: (empty) + - Stderr: (empty) +- **implement**: success + - Model: claude-opus-4-6, 89.1k tokens in / 13.5k out + - Files: /home/daytona/workspace/lib/crates/fabro-cli/src/main.rs, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/mod.rs, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/panic.rs, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/sender.rs, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/spawn.rs +- **simplify_opus**: success + - Model: claude-opus-4-6, 60.2k tokens in / 17.7k out + - Files: /home/daytona/workspace/Cargo.toml, /home/daytona/workspace/lib/crates/fabro-util/Cargo.toml, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/panic.rs, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/sender.rs, /home/daytona/workspace/lib/crates/fabro-util/src/telemetry/spawn.rs + + +# Simplify: Code Review and Cleanup + +Review all changed files for reuse, quality, and efficiency. Fix any issues found. + +## Phase 1: Identify Changes + +Run git diff (or git diff HEAD if there are staged changes) to see what changed. If there are no git changes, review the most recently modified files that the user mentioned or that you edited earlier in this conversation. + +## Phase 2: Launch Three Review Agents in Parallel + +Use the Agent tool to launch all three agents concurrently in a single message. Pass each agent the full diff so it has the complete context. + +### Agent 1: Code Reuse Review + +For each change: + +1. Search for existing utilities and helpers that could replace newly written code. Use Grep to find similar patterns elsewhere in the codebase — common locations are utility directories, shared modules, and files adjacent to the changed ones. +2. Flag any new function that duplicates existing functionality. Suggest the existing function to use instead. +3. Flag any inline logic that could use an existing utility — hand-rolled string manipulation, manual path handling, custom environment checks, ad-hoc type guards, and similar patterns are common candidates. + +Note: This is a greenfield app, so focus on maximizing simplicity and don't worry about changing things to achieve it. + +### Agent 2: Code Quality Review + +Review the same changes for hacky patterns: + +1. Redundant state: state that duplicates existing state, cached values that could be derived, observers/effects that could be direct calls +2. Parameter sprawl: adding new parameters to a function instead of generalizing or restructuring existing ones +3. Copy-paste with slight variation: near-duplicate code blocks that should be unified with a shared abstraction +4. Leaky abstractions: exposing internal details that should be encapsulated, or breaking existing abstraction boundaries +5. Stringly-typed code: using raw strings where constants, enums (string unions), or branded types already exist in the codebase + +Note: This is a greenfield app, so be aggressive in optimizing quality. + +### Agent 3: Efficiency Review + +Review the same changes for efficiency: + +1. Unnecessary work: redundant computations, repeated file reads, duplicate network/API calls, N+1 patterns +2. Missed concurrency: independent operations run sequentially when they could run in parallel +3. Hot-path bloat: new blocking work added to startup or per-request/per-render hot paths +4. Unnecessary existence checks: pre-checking file/resource existence before operating (TOCTOU anti-pattern) — operate directly and handle the error +5. Memory: unbounded data structures, missing cleanup, event listener leaks +6. Overly broad operations: reading entire files when only a portion is needed, loading all items when filtering for one + +## Phase 3: Fix Issues + +Wait for all three agents to complete. Aggregate their findings and fix each issue directly. If a finding is a false positive or not worth addressing, note it and move on — do not argue with the finding, just skip it. + +When done, briefly summarize what was fixed (or confirm the code was already clean). \ No newline at end of file diff --git a/nodes/simplify_gemini/provider_used.json b/nodes/simplify_gemini/provider_used.json new file mode 100644 index 000000000..c1905ff10 --- /dev/null +++ b/nodes/simplify_gemini/provider_used.json @@ -0,0 +1,5 @@ +{ + "mode": "agent", + "provider": "anthropic", + "model": "claude-opus-4-6" +} \ No newline at end of file diff --git a/nodes/simplify_gemini/response.md b/nodes/simplify_gemini/response.md new file mode 100644 index 000000000..7d472ef24 --- /dev/null +++ b/nodes/simplify_gemini/response.md @@ -0,0 +1,17 @@ +All clean. Build, tests, and clippy all pass. Here's a summary of what was fixed: + +## Summary + +Three fixes applied based on the review agents' findings: + +1. **Unified `send_to_segment` signature** (Quality, High) — Changed `send_to_segment` from taking `&Track` to taking `&Path`, matching `send_panic_to_sentry`'s signature. This moved the file-reading logic into the function itself, making both sender functions consistent and simplifying the `SendAnalytics` handler in `main.rs` from a 6-line async block to a single function call. + +2. **Removed redundant `release` in `ClientOptions`** (Quality, Low) — The Sentry event already carries `release` from `build_panic_event()`. Setting it again in `ClientOptions` was redundant (Sentry uses the event-level value when present). Simplified to `sentry::ClientOptions::default()`. + +3. **Used `crate::version::FABRO_VERSION`** (Reuse, Low) — Replaced raw `env!("CARGO_PKG_VERSION")` in `panic.rs` with the existing `crate::version::FABRO_VERSION` constant for consistency with the rest of the codebase. + +**Skipped findings (not worth addressing):** +- `panic_message()` duplication with `engine.rs` — Different crates, different prefixes, only 5 lines. Not worth cross-crate extraction. +- `dirs::home_dir().join(".fabro")` repeated 15+ times — Valid but out of scope for this diff. +- Stringly-typed subcommand names — clap requires string literals in attributes, so constants can only help 2 of 4 sites. Marginal benefit. +- All efficiency findings — No issues found; code is well-structured for its fire-and-forget telemetry purpose. \ No newline at end of file diff --git a/nodes/simplify_gemini/status.json b/nodes/simplify_gemini/status.json new file mode 100644 index 000000000..db3722d0f --- /dev/null +++ b/nodes/simplify_gemini/status.json @@ -0,0 +1,6 @@ +{ + "status": "success", + "notes": "Stage completed: simplify_gemini", + "failure_reason": null, + "timestamp": "2026-03-16T05:31:33.425699+00:00" +} \ No newline at end of file diff --git a/nodes/simplify_opus/diff.patch b/nodes/simplify_opus/diff.patch new file mode 100644 index 000000000..bc7d4d026 --- /dev/null +++ b/nodes/simplify_opus/diff.patch @@ -0,0 +1,204 @@ +diff --git a/Cargo.toml b/Cargo.toml +index 46e6be9..ef0725d 100644 +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -76,4 +76,4 @@ opt-level = 2 + [profile.dev.package.regex-automata] + opt-level = 2 + [profile.dev.package.regex-syntax] +-opt-level = 2 +\ No newline at end of file ++opt-level = 2 +diff --git a/lib/crates/fabro-util/Cargo.toml b/lib/crates/fabro-util/Cargo.toml +index 54b47d8..55c173d 100644 +--- a/lib/crates/fabro-util/Cargo.toml ++++ b/lib/crates/fabro-util/Cargo.toml +@@ -37,4 +37,4 @@ serde = { workspace = true } + + [dev-dependencies] + insta = { workspace = true } +-tempfile = "3" +\ No newline at end of file ++tempfile = "3" +diff --git a/lib/crates/fabro-util/src/telemetry/panic.rs b/lib/crates/fabro-util/src/telemetry/panic.rs +index 4ba2232..d21ee6d 100644 +--- a/lib/crates/fabro-util/src/telemetry/panic.rs ++++ b/lib/crates/fabro-util/src/telemetry/panic.rs +@@ -97,28 +97,8 @@ fn spawn_panic_sender(event: Event<'static>) { + Err(_) => return, + }; + +- let event_id = event.event_id; +- let filename = format!("fabro-panic-{event_id}.json"); +- +- let path = match super::spawn::write_temp_json(&filename, &json) { +- Some(p) => p, +- None => return, +- }; +- +- let path_str = match path.to_str() { +- Some(s) => s.to_string(), +- None => return, +- }; +- +- let exe = match super::spawn::current_exe_str() { +- Some(e) => e, +- None => return, +- }; +- +- super::spawn::spawn_detached( +- &[&exe, "__send_panic", &path_str], +- &[("FABRO_TELEMETRY", "off")], +- ); ++ let filename = format!("fabro-panic-{}.json", event.event_id); ++ super::spawn::spawn_fabro_subcommand("__send_panic", &filename, &json); + } + + /// Send a serialized Sentry panic event. Called by the `__send_panic` subcommand. +diff --git a/lib/crates/fabro-util/src/telemetry/sender.rs b/lib/crates/fabro-util/src/telemetry/sender.rs +index de8e113..27e636f 100644 +--- a/lib/crates/fabro-util/src/telemetry/sender.rs ++++ b/lib/crates/fabro-util/src/telemetry/sender.rs +@@ -27,26 +27,7 @@ fn spawn_sender(track: Track) { + }; + + let filename = format!("fabro-event-{}.json", track.message_id); +- +- let path = match super::spawn::write_temp_json(&filename, &json) { +- Some(p) => p, +- None => return, +- }; +- +- let path_str = match path.to_str() { +- Some(s) => s.to_string(), +- None => return, +- }; +- +- let exe = match super::spawn::current_exe_str() { +- Some(e) => e, +- None => return, +- }; +- +- super::spawn::spawn_detached( +- &[&exe, "__send_analytics", &path_str], +- &[("FABRO_TELEMETRY", "off")], +- ); ++ super::spawn::spawn_fabro_subcommand("__send_analytics", &filename, &json); + } + + /// Sends a track event to Segment. Called by the `__send_analytics` subcommand. +diff --git a/lib/crates/fabro-util/src/telemetry/spawn.rs b/lib/crates/fabro-util/src/telemetry/spawn.rs +index b69746e..8626def 100644 +--- a/lib/crates/fabro-util/src/telemetry/spawn.rs ++++ b/lib/crates/fabro-util/src/telemetry/spawn.rs +@@ -1,5 +1,3 @@ +-use std::path::Path; +- + /// Spawn a fully detached subprocess that survives parent exit and terminal close. + /// + /// On Unix this uses the double-fork pattern (fork → setsid → close_fd → fork → exec) +@@ -52,9 +50,8 @@ fn spawn_detached_unix(args: &[&str], env: &[(&str, &str)]) { + } + + // Replace the process with the target command. +- let err = exec::execvp(args[0], args); +- // If execvp returns, it failed. +- eprintln!("spawn_detached: exec failed: {err}"); ++ let _err = exec::execvp(args[0], args); ++ // If execvp returns, it failed. stderr is closed so we can't log. + std::process::exit(1); + } + Err(_) => std::process::exit(1), +@@ -88,62 +85,47 @@ fn spawn_detached_windows(args: &[&str], env: &[(&str, &str)]) { + } + } + +-/// Write JSON bytes to a temp file in `~/.fabro/tmp/` and return the path. +-/// Returns `None` if the home directory is unavailable or write fails. +-pub fn write_temp_json(filename: &str, json: &[u8]) -> Option { +- let tmp_dir = dirs::home_dir()?.join(".fabro").join("tmp"); +- std::fs::create_dir_all(&tmp_dir).ok()?; ++/// Serialize data as JSON to a temp file and spawn `fabro ` ++/// as a fully detached subprocess. Sets `FABRO_TELEMETRY=off` to prevent recursion. ++/// ++/// This is the shared pattern used by both analytics and panic senders. ++/// No-ops silently if the exe path can't be resolved or the temp file can't be written. ++pub fn spawn_fabro_subcommand(subcommand: &str, filename: &str, json: &[u8]) { ++ let tmp_dir = match dirs::home_dir() { ++ Some(h) => h.join(".fabro").join("tmp"), ++ None => return, ++ }; ++ if std::fs::create_dir_all(&tmp_dir).is_err() { ++ return; ++ } + let path = tmp_dir.join(filename); +- std::fs::write(&path, json).ok()?; +- Some(path) +-} ++ if std::fs::write(&path, json).is_err() { ++ return; ++ } + +-/// Build the argv for spawning a `fabro` hidden subcommand with a temp file path. +-pub fn build_fabro_argv<'a>(exe: &'a str, subcommand: &'a str, path: &'a str) -> Vec<&'a str> { +- vec![exe, subcommand, path] +-} ++ let path_str = match path.to_str() { ++ Some(s) => s.to_string(), ++ None => return, ++ }; + +-/// Convenience: get the current exe as a String, or None. +-pub fn current_exe_str() -> Option { +- std::env::current_exe() ++ let exe = match std::env::current_exe() + .ok() + .and_then(|p| p.to_str().map(|s| s.to_string())) +-} +- +-/// Check whether a path exists (for tests / cleanup verification). +-pub fn path_exists(path: &Path) -> bool { +- path.exists() ++ { ++ Some(e) => e, ++ None => return, ++ }; ++ ++ spawn_detached( ++ &[&exe, subcommand, &path_str], ++ &[("FABRO_TELEMETRY", "off")], ++ ); + } + + #[cfg(test)] + mod tests { + use super::*; + +- #[test] +- fn write_temp_json_creates_file() { +- let json = br#"{"test": true}"#; +- let path = write_temp_json("fabro-test-spawn.json", json).expect("write_temp_json failed"); +- assert!(path.exists()); +- let contents = std::fs::read(&path).unwrap(); +- assert_eq!(contents, json); +- std::fs::remove_file(&path).ok(); +- } +- +- #[test] +- fn build_fabro_argv_correct() { +- let argv = build_fabro_argv("/usr/bin/fabro", "__send_analytics", "/tmp/event.json"); +- assert_eq!( +- argv, +- vec!["/usr/bin/fabro", "__send_analytics", "/tmp/event.json"] +- ); +- } +- +- #[test] +- fn current_exe_str_returns_some() { +- // In test context there is always a current exe. +- assert!(current_exe_str().is_some()); +- } +- + #[test] + fn spawn_detached_empty_args_is_noop() { + // Should not panic or do anything.