From 4a5f9f698fd7585eef2596c6599e57480c7adbd0 Mon Sep 17 00:00:00 2001
From: "brynary-fabro[bot]"
<265161896+brynary-fabro[bot]@users.noreply.github.com>
Date: Thu, 19 Mar 2026 22:27:03 -0400
Subject: [PATCH] Strip markdown headings and "Plan:" prefix from GOAL column
in `fabro ps` (#111)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This change cleans up the `GOAL` column in `fabro ps` output by
stripping common markdown noise before displaying goal text.
Specifically, `truncate_goal` now removes leading `#` heading markers
and an optional `Plan:` prefix (applied in order, so `## Plan: migrate
DB` correctly becomes `migrate DB`). Whitespace is trimmed after each
strip step to avoid leading spaces in the result.
As a minor refactor, the truncation logic was extracted into a
standalone `truncate_str` helper. This allows `truncate_goal` to
delegate cleanly after preprocessing, and also lets the workflow name
column (which doesn't need heading/prefix stripping) call `truncate_str`
directly. Five new unit tests cover the stripping behavior, combined
stripping, passthrough of plain text, and that truncation still applies
correctly after stripping.
### Fabro Details
Ran 9 stages in 9m 0s for $1.17
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 1m 10s | – | 0 |
| preflight_lint | 12s | – | 0 |
| implement | 2m 28s | $0.42 | 0 |
| simplify_opus | 3m 20s | $0.75 | 0 |
| simplify_gpt | 0s | – | 0 |
| verify | 1m 20s | – | 0 |
| fmt | 1s | – | 0 |
| **Total** | **9m 0s** | **$1.17** | **0** |
Ran ImplementAndSimplify.fabro (12 nodes and 15
edges)
```dot
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_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_gpt -> verify
verify -> fmt [condition="outcome=success"]
verify -> fixup
fixup -> verify
fmt -> exit
}
```
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: Fabro
---
lib/crates/fabro-cli/src/commands/runs.rs | 44 ++++++++++++++++++++---
1 file changed, 40 insertions(+), 4 deletions(-)
diff --git a/lib/crates/fabro-cli/src/commands/runs.rs b/lib/crates/fabro-cli/src/commands/runs.rs
index 129b0c5c5..22632964e 100644
--- a/lib/crates/fabro-cli/src/commands/runs.rs
+++ b/lib/crates/fabro-cli/src/commands/runs.rs
@@ -228,11 +228,17 @@ fn short_run_id(id: &str) -> &str {
fn truncate_goal(goal: &str, max_len: usize) -> String {
let line = goal.lines().next().unwrap_or("");
- let char_count = line.chars().count();
+ let line = line.trim_start_matches('#').trim();
+ let line = line.strip_prefix("Plan:").map(|s| s.trim()).unwrap_or(line);
+ truncate_str(line, max_len)
+}
+
+fn truncate_str(s: &str, max_len: usize) -> String {
+ let char_count = s.chars().count();
if char_count <= max_len {
- return line.to_string();
+ return s.to_string();
}
- let truncated: String = line.chars().take(max_len - 3).collect();
+ let truncated: String = s.chars().take(max_len - 3).collect();
format!("{truncated}...")
}
@@ -404,7 +410,7 @@ fn df_from(args: &DfArgs, data_dir: &Path, runs_base: &Path, logs_base: &Path) -
};
vec![
short_run_id(&detail.run_id).cell(),
- truncate_goal(&detail.workflow_name, 16).cell(),
+ truncate_str(&detail.workflow_name, 16).cell(),
detail.status.to_string().cell(),
age.cell().justify(Justify::Right),
size_display.cell().justify(Justify::Right),
@@ -588,4 +594,34 @@ mod tests {
assert_eq!(format_size(1024), "1.0 KB");
assert_eq!(format_size(1024 * 1024), "1.0 MB");
}
+
+ #[test]
+ fn truncate_goal_strips_markdown_headings() {
+ assert_eq!(truncate_goal("## Fix bug", 50), "Fix bug");
+ assert_eq!(truncate_goal("# Title", 50), "Title");
+ assert_eq!(truncate_goal("### Deep heading", 50), "Deep heading");
+ }
+
+ #[test]
+ fn truncate_goal_strips_plan_prefix() {
+ assert_eq!(truncate_goal("Plan: do stuff", 50), "do stuff");
+ }
+
+ #[test]
+ fn truncate_goal_strips_heading_and_plan_prefix() {
+ assert_eq!(truncate_goal("## Plan: migrate DB", 50), "migrate DB");
+ }
+
+ #[test]
+ fn truncate_goal_plain_text_unchanged() {
+ assert_eq!(truncate_goal("Fix the login bug", 50), "Fix the login bug");
+ }
+
+ #[test]
+ fn truncate_goal_still_truncates_after_stripping() {
+ assert_eq!(
+ truncate_goal("## A long goal description", 10),
+ "A long ..."
+ );
+ }
}