mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-09 03:20:56 +00:00
parent
6cbd3372af
commit
7003cc1ea5
3 changed files with 56 additions and 0 deletions
38
graph.fabro
Normal file
38
graph.fabro
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
digraph ImplementAndSimplify {
|
||||
graph [
|
||||
goal="Implement and simplify",
|
||||
model_stylesheet="
|
||||
* { backend: api; model: claude-opus-4-6;}
|
||||
"
|
||||
]
|
||||
rankdir=LR
|
||||
|
||||
start [shape=Mdiamond, label="Start"]
|
||||
exit [shape=Msquare, label="Exit"]
|
||||
|
||||
toolchain [label="Toolchain", shape=parallelogram, 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", max_retries=0]
|
||||
preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check -q --workspace 2>&1", max_retries=0]
|
||||
preflight_lint [label="Preflight Lint", shape=parallelogram, script="cargo clippy -q --workspace -- -D warnings 2>&1", max_retries=0]
|
||||
fix_lints [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
|
||||
implement [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD."]
|
||||
simplify_opus [label="Simplify (Opus)", prompt="@prompts/simplify.md"]
|
||||
simplify_gemini [label="Simplify (Gemini)", prompt="@prompts/simplify.md", model="gemini-3.1-pro-preview-customtools"]
|
||||
simplify_gpt [label="Simplify (GPT-54)", prompt="@prompts/simplify.md", model="gpt-54"]
|
||||
verify [label="Verify", shape=parallelogram, script="cargo clippy -q --workspace -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 2>&1", goal_gate=true, retry_target="fixup"]
|
||||
fixup [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all clippy lint warnings and test failures.", max_visits=3]
|
||||
fmt [label="Format", shape=parallelogram, script="cargo fmt --all 2>&1", max_retries=0]
|
||||
|
||||
start -> toolchain
|
||||
toolchain -> preflight_compile [condition="outcome=success"]
|
||||
toolchain -> exit
|
||||
preflight_compile -> preflight_lint [condition="outcome=success"]
|
||||
preflight_compile -> exit
|
||||
preflight_lint -> implement [condition="outcome=success"]
|
||||
preflight_lint -> fix_lints
|
||||
fix_lints -> preflight_lint
|
||||
implement -> simplify_opus -> simplify_gemini -> simplify_gpt -> verify
|
||||
verify -> fmt [condition="outcome=success"]
|
||||
verify -> fixup
|
||||
fixup -> verify
|
||||
fmt -> exit
|
||||
}
|
||||
13
manifest.json
Normal file
13
manifest.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"run_id": "01KM3AVN0Z99W7AX4PCVEB8C9J",
|
||||
"workflow_name": "ImplementAndSimplify",
|
||||
"goal": "# Plan: JSONL analytics event file format\n\n## Context\n\nCurrently each CLI invocation writes a single `Track` event as a standalone JSON file (`~/.fabro/tmp/fabro-event-{uuid}.json`) and spawns a detached subprocess to send it. We want to switch to JSONL format (one JSON event per line) so a single file can contain multiple events. Filenames keep a UUID for uniqueness. This enables callers to batch multiple events into one file/subprocess.\n\nThe panic sender (`__send_panic`) is unaffected — it stays single-JSON-per-file.\n\n## Changes\n\n### 1. `lib/crates/fabro-util/src/telemetry/sender.rs` — rewrite\n\n**Writer — rename `send()` to `emit()`, accept multiple events:**\n- `pub fn emit(tracks: &[Track])` (was `pub fn send(track: Track)`)\n- Early return if `SEGMENT_WRITE_KEY` is `None` or `tracks` is empty\n- Generate a UUID for the filename: `fabro-events-{uuid}.jsonl`\n- Serialize each `Track` as a compact JSON line (`serde_json::to_string`), join with `\\n`\n- Pass the bytes to `spawn_fabro_subcommand(\"__send_analytics\", &filename, &json)` as before\n\nNo file locking needed — each invocation writes its own uniquely-named file.\n\n**Reader — rename `send_to_segment()` to `upload()`:**\n- Read file contents as string\n- Parse each non-empty line as `serde_json::Value`, inject `\"type\": \"track\"`, collect into batch array\n- Skip malformed lines with `tracing::warn!`\n- If no valid events, return `Ok(())`\n- POST to `https://api.segment.io/v1/batch` with payload `{\"batch\": [...]}`\n- Keep Basic auth the same\n\nExtract a pure `fn build_segment_batch(content: &str) -> Option<Value>` for testability.\n\n**Constants:**\n- Change `SEGMENT_API_URL` from `.../v1/track` to `.../v1/batch`\n\n### 2. `lib/crates/fabro-cli/src/main.rs`\n\n**`send_telemetry_event()` (~line 428):** Change call from `sender::send(track)` to `sender::emit(&[track])`.\n\n**`SendAnalytics` handler (~line 910):** Change call from `sender::send_to_segment(&path)` to `sender::upload(&path)`.\n\n### 3. `lib/crates/fabro-util/src/telemetry/spawn.rs` — no changes\n\n`spawn_fabro_subcommand` is generic (takes raw bytes). It continues to work for both JSONL analytics files and single-JSON panic files.\n\n### 4. No changes to these files\n- `event.rs` — `Track` struct unchanged\n- `panic.rs` — stays single-JSON-per-file\n- `mod.rs`, `anonymous_id.rs`, `context.rs`, `git.rs`, `sanitize.rs` — unchanged\n\n## Implementation order (red/green TDD)\n\nWrite each test first (red), then implement just enough to make it pass (green).\n\n### Step 1: `build_segment_batch` — pure function, no I/O\n\n1. **Red:** Write test `build_segment_batch_empty_content` — empty string returns `None`\n2. **Green:** Add `fn build_segment_batch(content: &str) -> Option<Value>` stub returning `None`\n3. **Red:** Write test `build_segment_batch_single_event` — one JSONL line produces `{\"batch\": [{\"type\": \"track\", ...}]}`\n4. **Green:** Implement line parsing, `\"type\": \"track\"` injection, batch wrapping\n5. **Red:** Write test `build_segment_batch_multiple_events` — two lines produce batch of 2\n6. **Green:** Should already pass\n7. **Red:** Write test `build_segment_batch_skips_malformed_lines` — one good + one bad line produces batch of 1\n8. **Green:** Add `continue` on parse error\n\n### Step 2: `emit()` — writer side\n\n9. **Red:** Update existing `send_noops_without_write_key` to use `emit(&[track])` signature\n10. **Green:** Rename `send` to `emit`, change signature to `&[Track]`, serialize as JSONL (one JSON line per track, joined with `\\n`), generate `fabro-events-{uuid}.jsonl` filename\n\n### Step 3: `upload()` — reader side\n\n11. **Red:** Write test `upload_noops_without_write_key` — same pattern as existing `send_panic_noops_without_dsn`\n12. **Green:** Rename `send_to_segment` to `upload`, change internals to read file as string, call `build_segment_batch`, POST to `/v1/batch`\n\n### Step 4: Wire up call sites in `main.rs`\n\n13. Update `send_telemetry_event()` to call `sender::emit(&[track])`\n14. Update `SendAnalytics` handler to call `sender::upload(&path)`\n\n### Step 5: Final checks\n\n```bash\ncargo fmt --check --all\ncargo clippy --workspace -- -D warnings\ncargo test -p fabro-util\ncargo test --workspace\n```\n",
|
||||
"start_time": "2026-03-19T15:19:15.281808Z",
|
||||
"node_count": 13,
|
||||
"edge_count": 16,
|
||||
"run_branch": "fabro/run/01KM3AVN0Z99W7AX4PCVEB8C9J",
|
||||
"base_sha": "a5273e17a3aa92767039a542eaacf579ccb84779",
|
||||
"base_branch": "main",
|
||||
"workflow_slug": "implement",
|
||||
"host_repo_path": "/Users/bhelmkamp/p/fabro-sh/fabro"
|
||||
}
|
||||
5
sandbox.json
Normal file
5
sandbox.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"provider": "daytona",
|
||||
"working_directory": "/home/daytona/workspace",
|
||||
"identifier": "fabro-01KM3AVN0Z99W7AX4PCVEB8C9J"
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue