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:
@@ -239,11 +239,32 @@ impl LoreError {
|
||||
self.code().exit_code()
|
||||
}
|
||||
|
||||
pub fn actions(&self) -> Vec<&'static str> {
|
||||
match self {
|
||||
Self::ConfigNotFound { .. } => vec!["lore init"],
|
||||
Self::ConfigInvalid { .. } => vec!["lore init --force"],
|
||||
Self::GitLabAuthFailed => {
|
||||
vec!["export GITLAB_TOKEN=glpat-xxx", "lore auth"]
|
||||
}
|
||||
Self::TokenNotSet { .. } => vec!["export GITLAB_TOKEN=glpat-xxx"],
|
||||
Self::OllamaUnavailable { .. } => vec!["ollama serve"],
|
||||
Self::OllamaModelNotFound { .. } => vec!["ollama pull nomic-embed-text"],
|
||||
Self::DatabaseLocked { .. } => vec!["lore ingest --force"],
|
||||
Self::EmbeddingsNotBuilt => vec!["lore embed"],
|
||||
Self::EmbeddingFailed { .. } => vec!["lore embed --retry-failed"],
|
||||
Self::MigrationFailed { .. } => vec!["lore migrate"],
|
||||
Self::GitLabNetworkError { .. } => vec!["lore doctor"],
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_robot_error(&self) -> RobotError {
|
||||
let actions = self.actions().into_iter().map(String::from).collect();
|
||||
RobotError {
|
||||
code: self.code().to_string(),
|
||||
message: self.to_string(),
|
||||
suggestion: self.suggestion().map(String::from),
|
||||
actions,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,6 +275,8 @@ pub struct RobotError {
|
||||
pub message: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub suggestion: Option<String>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub actions: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
||||
Reference in New Issue
Block a user