feat(robot): add elapsed_ms timing, --fields support, and actionable error actions

Robot mode consistency improvements across all command output:

Timing:
- Every robot JSON response now includes meta.elapsed_ms measuring
  wall-clock time from command start to serialization. Agents can use
  this to detect slow queries and tune --limit or --project filters.

Field selection (--fields):
- print_list_issues_json and print_list_mrs_json accept an optional
  fields slice that prunes each item in the response array to only
  the requested keys. A "minimal" preset expands to
  [iid, title, state, updated_at_iso] for token-efficient agent scans.
- filter_fields and expand_fields_preset live in the new
  src/cli/robot.rs module alongside RobotMeta.

Actionable error recovery:
- LoreError gains an actions() method returning concrete shell commands
  an agent can execute to recover (e.g. "ollama serve" for
  OllamaUnavailable, "lore init" for ConfigNotFound).
- RobotError now serializes an "actions" array (empty array omitted)
  so agents can parse and offer one-click fixes.

Envelope consistency:
- show issue/MR JSON responses now use the standard
  {"ok":true,"data":...,"meta":...} envelope instead of bare data,
  matching all other commands.

Files: src/cli/robot.rs (new), src/core/error.rs,
       src/cli/commands/{count,embed,generate_docs,ingest,list,show,stats,sync_status}.rs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Taylor Eernisse
2026-02-06 23:46:48 -05:00
parent 4ce0130620
commit cf6d27435a
10 changed files with 138 additions and 15 deletions

View File

@@ -3,6 +3,7 @@ use rusqlite::Connection;
use serde::Serialize;
use crate::Config;
use crate::cli::robot::RobotMeta;
use crate::core::db::create_connection;
use crate::core::error::Result;
use crate::core::events_db::{self, EventCounts};
@@ -196,6 +197,7 @@ fn format_number(n: i64) -> String {
struct CountJsonOutput {
ok: bool,
data: CountJsonData,
meta: RobotMeta,
}
#[derive(Serialize)]
@@ -228,6 +230,7 @@ pub fn run_count_events(config: &Config) -> Result<EventCounts> {
struct EventCountJsonOutput {
ok: bool,
data: EventCountJsonData,
meta: RobotMeta,
}
#[derive(Serialize)]
@@ -245,7 +248,7 @@ struct EventTypeCounts {
total: usize,
}
pub fn print_event_count_json(counts: &EventCounts) {
pub fn print_event_count_json(counts: &EventCounts, elapsed_ms: u64) {
let output = EventCountJsonOutput {
ok: true,
data: EventCountJsonData {
@@ -266,6 +269,7 @@ pub fn print_event_count_json(counts: &EventCounts) {
},
total: counts.total(),
},
meta: RobotMeta { elapsed_ms },
};
println!("{}", serde_json::to_string(&output).unwrap());
@@ -317,7 +321,7 @@ pub fn print_event_count(counts: &EventCounts) {
);
}
pub fn print_count_json(result: &CountResult) {
pub fn print_count_json(result: &CountResult, elapsed_ms: u64) {
let breakdown = result.state_breakdown.as_ref().map(|b| CountJsonBreakdown {
opened: b.opened,
closed: b.closed,
@@ -333,6 +337,7 @@ pub fn print_count_json(result: &CountResult) {
system_excluded: result.system_count,
breakdown,
},
meta: RobotMeta { elapsed_ms },
};
println!("{}", serde_json::to_string(&output).unwrap());