Add /graph G5: box-drawing DAG rendering for /graph show
/graph show now renders the dependency graph as box-drawing text via a deterministic Sugiyama-lite pipeline in session/graph_render.rs: longest-path layering, dagre-style dummy pass-throughs so every drawn edge spans exactly one layer gap (long edges route as vertical lanes through intermediate bands), one-pass barycenter ordering, and greedy bus-lane allocation in the connector gutters with box-drawing-aware glyph merging (a bus crossing a pass-through renders ┼). Node boxes carry a status glyph (✓ ▶ ○ · ✗ ⊘) and a clamped title; a legend line closes the view. Only Blocks edges are drawn — DiscoveredFrom is audit metadata whose origin is always terminal, so drawing it would double edges without scheduling meaning. The output rides ordinary scrollback (the pager already scrolls it), so no new wire variant or pager view was needed. Honest ceiling: a graph wider than the 120-column budget — or an empty/pre-planning graph — degrades to the indented status tree, because box art wrapped by the terminal is worse than no art; a cycle reaching the renderer (upstream validation bypassed) refuses to render rather than looping. Tests: six-node structural snapshot (layering order, fan-in bands, arrowheads, corners, legend, no trailing whitespace, width bound), determinism across runs, too-wide fallback, dummy-lane routing through intermediate bands, DiscoveredFrom exclusion, title clamping, empty graph; plus a handle_prompt e2e driving /graph show end to end (no-graph degrade + seeded chain rendering). kigi-shell 4949 lib tests green; workspace clippy clean.
This commit is contained in:
1 parent
771de1822d
commit
db05ed0751
5 files changed
+611
-7
No files matched your search
@@ -819,6 +819,22 @@ impl SessionActor {
|
||||
self.send_slash_command_output(&msg).await;
|
||||
ok_end_turn(0, None)
|
||||
}
|
||||
BuiltinAction::GraphShow => {
|
||||
// Box-drawing DAG; wider than the budget (or no graph)
|
||||
// degrades to the indented status tree — wrapped box art
|
||||
// is worse than no art.
|
||||
const SHOW_WIDTH_BUDGET: usize = 120;
|
||||
let rendered =
|
||||
self.graph_tracker.lock().snapshot().and_then(|s| {
|
||||
crate::session::graph_render::render_dag(s, SHOW_WIDTH_BUDGET)
|
||||
});
|
||||
let msg = match rendered {
|
||||
Some(dag) => dag,
|
||||
None => self.graph_status_message().await,
|
||||
};
|
||||
self.send_slash_command_output(&msg).await;
|
||||
ok_end_turn(0, None)
|
||||
}
|
||||
BuiltinAction::GraphPause => {
|
||||
use crate::session::goal_tracker::{GoalPauseReason, GoalStatus};
|
||||
let (msg, changed) = {
|
||||
|
||||
Reference in new issue
Block a user