mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-08 03:10:26 +00:00
Enrich tool call display, emit exit node events, add --no-retro and demo
- Show tool args in dim parenthetical: read_file(CLAUDE.md) - Shorten CWD-relative paths in tool call display - Show elapsed time on completed tool calls - Emit StageStarted/StageCompleted for terminal (exit) nodes - Add --no-retro flag to skip retro generation - Add demo/02-tool-use.dot, renumber existing demos 03-07 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
23a943efca
commit
4eea33b797
11 changed files with 190 additions and 16 deletions
|
|
@ -123,6 +123,10 @@ pub struct RunArgs {
|
|||
/// Attach a label to this run (repeatable, format: KEY=VALUE)
|
||||
#[arg(long = "label", value_name = "KEY=VALUE")]
|
||||
pub label: Vec<String>,
|
||||
|
||||
/// Skip retro generation after the run
|
||||
#[arg(long)]
|
||||
pub no_retro: bool,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ cached_style!(
|
|||
style_tool_running,
|
||||
" {spinner:.dim} {wide_msg} {elapsed:.dim}"
|
||||
);
|
||||
cached_style!(style_tool_done, " {wide_msg}");
|
||||
cached_style!(style_tool_done, " {wide_msg} {prefix:.dim}");
|
||||
cached_style!(style_static_dim, " {wide_msg:.dim}");
|
||||
cached_style!(style_empty, " ");
|
||||
|
||||
|
|
@ -76,20 +76,44 @@ fn format_duration_ms(ms: u64) -> String {
|
|||
|
||||
// ── Tool call display name ──────────────────────────────────────────────
|
||||
|
||||
fn tool_display_name(tool_name: &str, arguments: &serde_json::Value) -> String {
|
||||
if tool_name == "bash" || tool_name == "execute_command" {
|
||||
if let Some(cmd) = arguments.get("command").and_then(|v| v.as_str()) {
|
||||
let truncated: String = if cmd.len() > 60 {
|
||||
let mut s: String = cmd.chars().take(57).collect();
|
||||
s.push_str("...");
|
||||
s
|
||||
} else {
|
||||
cmd.to_string()
|
||||
};
|
||||
return format!("bash: {truncated}");
|
||||
fn truncate(s: &str, max: usize) -> String {
|
||||
if s.len() > max {
|
||||
let mut t: String = s.chars().take(max - 3).collect();
|
||||
t.push_str("...");
|
||||
t
|
||||
} else {
|
||||
s.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn shorten_path(path: &str) -> String {
|
||||
if let Ok(cwd) = std::env::current_dir() {
|
||||
if let Ok(rel) = std::path::Path::new(path).strip_prefix(&cwd) {
|
||||
return rel.display().to_string();
|
||||
}
|
||||
}
|
||||
tool_name.to_string()
|
||||
path.to_string()
|
||||
}
|
||||
|
||||
fn tool_display_name(tool_name: &str, arguments: &serde_json::Value) -> String {
|
||||
let dim = Style::new().dim();
|
||||
let arg = |key: &str| arguments.get(key).and_then(|v| v.as_str());
|
||||
let path_arg = || arg("path").or_else(|| arg("file_path")).map(|p| truncate(&shorten_path(p), 60));
|
||||
|
||||
let detail = match tool_name {
|
||||
"bash" | "execute_command" => arg("command").map(|c| truncate(c, 60)),
|
||||
"glob" => arg("pattern").map(String::from),
|
||||
"grep" | "ripgrep" => arg("pattern").map(|p| truncate(p, 40)),
|
||||
"read_file" | "read" => path_arg(),
|
||||
"write_file" | "write" | "create_file" => path_arg(),
|
||||
"edit_file" | "edit" => path_arg(),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
match detail {
|
||||
Some(d) => format!("{tool_name}{}", dim.apply_to(format!("({d})"))),
|
||||
None => tool_name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tool call entry ─────────────────────────────────────────────────────
|
||||
|
|
@ -448,7 +472,9 @@ impl ProgressUI {
|
|||
} else {
|
||||
ToolCallStatus::Succeeded
|
||||
};
|
||||
let elapsed = format_duration_short(entry.bar.elapsed());
|
||||
entry.bar.set_style(style_tool_done());
|
||||
entry.bar.set_prefix(elapsed);
|
||||
entry
|
||||
.bar
|
||||
.finish_with_message(format!("{glyph} {}", entry.display_name));
|
||||
|
|
|
|||
|
|
@ -661,7 +661,7 @@ pub async fn run_command(
|
|||
}
|
||||
|
||||
// Auto-derive retro (always, cheap) and optionally run retro agent
|
||||
{
|
||||
if !args.no_retro {
|
||||
let (failed, failure_reason) = match &engine_result {
|
||||
Ok(o) => (
|
||||
o.status == StageStatus::Fail,
|
||||
|
|
@ -972,7 +972,7 @@ async fn run_from_branch(
|
|||
let _ = crate::git::remove_worktree(&original_cwd, &worktree_path);
|
||||
|
||||
// Auto-derive retro
|
||||
{
|
||||
if !args.no_retro {
|
||||
let (failed, failure_reason) = match &engine_result {
|
||||
Ok(o) => (
|
||||
o.status == StageStatus::Fail,
|
||||
|
|
|
|||
|
|
@ -1246,7 +1246,36 @@ impl WorkflowRunEngine {
|
|||
// Step 1: Check for terminal node
|
||||
if is_terminal(node) {
|
||||
match check_goal_gates(graph, &node_outcomes) {
|
||||
Ok(()) => break,
|
||||
Ok(()) => {
|
||||
self.services
|
||||
.emitter
|
||||
.emit(&WorkflowRunEvent::StageStarted {
|
||||
node_id: node.id.clone(),
|
||||
name: node.label().to_string(),
|
||||
index: stage_index,
|
||||
handler_type: node.handler_type().map(String::from),
|
||||
attempt: 1,
|
||||
max_attempts: 1,
|
||||
});
|
||||
self.services
|
||||
.emitter
|
||||
.emit(&WorkflowRunEvent::StageCompleted {
|
||||
node_id: node.id.clone(),
|
||||
name: node.label().to_string(),
|
||||
index: stage_index,
|
||||
duration_ms: 0,
|
||||
status: StageStatus::Success.to_string(),
|
||||
preferred_label: None,
|
||||
suggested_next_ids: vec![],
|
||||
usage: None,
|
||||
failure: None,
|
||||
notes: None,
|
||||
files_touched: vec![],
|
||||
attempt: 1,
|
||||
max_attempts: 1,
|
||||
});
|
||||
break;
|
||||
}
|
||||
Err(failed_node_id) => {
|
||||
if let Some(retry_target) = get_retry_target(&failed_node_id, graph) {
|
||||
current_node_id = retry_target;
|
||||
|
|
|
|||
11
demo/01-hello.dot
Normal file
11
demo/01-hello.dot
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
digraph Hello {
|
||||
graph [goal="Write a haiku about software workflows"]
|
||||
rankdir=LR
|
||||
|
||||
start [shape=Mdiamond, label="Start"]
|
||||
exit [shape=Msquare, label="Exit"]
|
||||
|
||||
compose [label="Compose", prompt="Write a haiku (5-7-5 syllable) about software workflows. Output only the haiku, nothing else.", codergen_mode="one_shot", reasoning_effort="low"]
|
||||
|
||||
start -> compose -> exit
|
||||
}
|
||||
11
demo/02-tool-use.dot
Normal file
11
demo/02-tool-use.dot
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
digraph ToolUse {
|
||||
graph [goal="Explore the current directory using shell tools"]
|
||||
rankdir=LR
|
||||
|
||||
start [shape=Mdiamond, label="Start"]
|
||||
exit [shape=Msquare, label="Exit"]
|
||||
|
||||
explore [label="Explore", prompt="Use bash to list the files in the current directory, then read the first 5 lines of any README or CLAUDE.md file you find. Summarize what this project is about in 2-3 sentences."]
|
||||
|
||||
start -> explore -> exit
|
||||
}
|
||||
13
demo/03-pipeline.dot
Normal file
13
demo/03-pipeline.dot
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
digraph Pipeline {
|
||||
graph [goal="Analyze the current directory and suggest improvements"]
|
||||
rankdir=LR
|
||||
|
||||
start [shape=Mdiamond, label="Start"]
|
||||
exit [shape=Msquare, label="Exit"]
|
||||
|
||||
scan [label="Scan Files", shape=parallelogram, script="find . -maxdepth 2 -type f | head -30"]
|
||||
analyze [label="Analyze", prompt="Review the file listing from the previous step. Identify what kind of project this is and summarize its structure in 3-4 bullet points.", codergen_mode="one_shot", reasoning_effort="low"]
|
||||
suggest [label="Suggest", prompt="Based on the analysis, suggest 3 concrete improvements to the project structure. Be specific and actionable.", codergen_mode="one_shot", reasoning_effort="low"]
|
||||
|
||||
start -> scan -> analyze -> suggest -> exit
|
||||
}
|
||||
16
demo/04-branch-loop.dot
Normal file
16
demo/04-branch-loop.dot
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
digraph BranchLoop {
|
||||
graph [goal="Create a Python script that passes its test suite"]
|
||||
rankdir=LR
|
||||
|
||||
start [shape=Mdiamond, label="Start"]
|
||||
exit [shape=Msquare, label="Exit"]
|
||||
|
||||
plan [label="Plan", prompt="Plan a small Python script (fizzbuzz.py) and a test file (test_fizzbuzz.py) using pytest. Describe what you will create.", codergen_mode="one_shot", reasoning_effort="low"]
|
||||
implement [label="Implement", prompt="Create fizzbuzz.py and test_fizzbuzz.py as planned. Write the files to disk."]
|
||||
validate [label="Validate", shape=parallelogram, script="python -m pytest test_fizzbuzz.py -v 2>&1 || true"]
|
||||
gate [shape=diamond, label="Tests passing?"]
|
||||
|
||||
start -> plan -> implement -> validate -> gate
|
||||
gate -> exit [label="Pass", condition="outcome=success"]
|
||||
gate -> implement [label="Fix"]
|
||||
}
|
||||
25
demo/05-parallel.dot
Normal file
25
demo/05-parallel.dot
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
digraph Parallel {
|
||||
graph [goal="Perform a multi-perspective code review"]
|
||||
rankdir=LR
|
||||
|
||||
start [shape=Mdiamond, label="Start"]
|
||||
exit [shape=Msquare, label="Exit"]
|
||||
|
||||
fork [label="Fork Analysis", shape=component, join_policy="wait_all", error_policy="continue"]
|
||||
|
||||
security [label="Security Audit", prompt="Examine the codebase for security concerns: hardcoded secrets, injection risks, unsafe dependencies. List findings as bullet points.", codergen_mode="one_shot", reasoning_effort="low"]
|
||||
architecture [label="Architecture Review", prompt="Assess the codebase architecture: separation of concerns, dependency structure, modularity. List findings as bullet points.", codergen_mode="one_shot", reasoning_effort="low"]
|
||||
quality [label="Code Quality", prompt="Check code quality: naming conventions, dead code, test coverage gaps, error handling. List findings as bullet points.", codergen_mode="one_shot", reasoning_effort="low"]
|
||||
|
||||
merge [label="Merge Findings", shape=tripleoctagon]
|
||||
report [label="Final Report", prompt="Synthesize the security, architecture, and code quality findings into a prioritized summary report with top 5 action items.", codergen_mode="one_shot"]
|
||||
|
||||
start -> fork
|
||||
fork -> security
|
||||
fork -> architecture
|
||||
fork -> quality
|
||||
security -> merge
|
||||
architecture -> merge
|
||||
quality -> merge
|
||||
merge -> report -> exit
|
||||
}
|
||||
18
demo/06-human-gate.dot
Normal file
18
demo/06-human-gate.dot
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
digraph HumanGate {
|
||||
graph [goal="Propose and implement a README improvement"]
|
||||
rankdir=LR
|
||||
|
||||
start [shape=Mdiamond, label="Start"]
|
||||
exit [shape=Msquare, label="Exit"]
|
||||
|
||||
draft [label="Draft Proposal", prompt="Read the README.md (or note its absence). Propose a specific improvement: either create one or enhance the existing one. Describe your proposed changes clearly but do NOT make any changes yet.", codergen_mode="one_shot"]
|
||||
approve [label="Approve Changes?", shape=hexagon]
|
||||
apply [label="Apply Changes", prompt="Apply the proposed README changes that were approved."]
|
||||
skip [label="Skip", prompt="Acknowledged. No changes made.", codergen_mode="one_shot", reasoning_effort="low"]
|
||||
|
||||
start -> draft -> approve
|
||||
approve -> apply [label="[A] Approve"]
|
||||
approve -> skip [label="[S] Skip"]
|
||||
apply -> exit
|
||||
skip -> exit
|
||||
}
|
||||
21
demo/07-multi-model.dot
Normal file
21
demo/07-multi-model.dot
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
digraph MultiModel {
|
||||
graph [
|
||||
goal="Build and review a utility function using multiple models",
|
||||
model_stylesheet="
|
||||
* { llm_model: claude-haiku-4-5; llm_provider: anthropic; reasoning_effort: low; }
|
||||
.coding { llm_model: claude-sonnet-4-5; llm_provider: anthropic; reasoning_effort: high; }
|
||||
#review { llm_model: claude-sonnet-4-5; llm_provider: anthropic; reasoning_effort: high; }
|
||||
"
|
||||
]
|
||||
rankdir=LR
|
||||
|
||||
start [shape=Mdiamond, label="Start"]
|
||||
exit [shape=Msquare, label="Exit"]
|
||||
|
||||
spec [label="Write Spec", prompt="Write a brief spec for a TypeScript string utility module with 3 functions: slugify, truncate, and capitalize. Output the spec only.", codergen_mode="one_shot"]
|
||||
implement [label="Implement", prompt="Implement the TypeScript string utility module from the spec. Write it to string-utils.ts.", class="coding"]
|
||||
test [label="Write Tests", prompt="Write tests for the string utility module using Bun's test runner. Write to string-utils.test.ts.", class="coding"]
|
||||
review [label="Code Review", prompt="Review the implementation and tests. Check for edge cases, type safety, and correctness. Provide a brief verdict.", codergen_mode="one_shot"]
|
||||
|
||||
start -> spec -> implement -> test -> review -> exit
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue