Files
gitlore/src/cli/robot.rs
teernisse 83cd16c918 feat: implement per-note search and document pipeline
- Add SourceType::Note with extract_note_document() and ParentMetadataCache
- Migration 022: composite indexes for notes queries + author_id column
- Migration 024: table rebuild adding 'note' to CHECK constraints, defense triggers
- Migration 025: backfill existing non-system notes into dirty queue
- Add lore notes CLI command with 17 filter options (author, path, resolution, etc.)
- Support table/json/jsonl/csv output formats with field selection
- Wire note dirty tracking through discussion and MR discussion ingestion
- Fix test_migration_024_preserves_existing_data off-by-one (tested wrong migration)
- Fix upsert_document_inner returning false for label/path-only changes
2026-02-12 13:31:24 -05:00

111 lines
3.5 KiB
Rust

use serde::Serialize;
#[derive(Debug, Serialize)]
pub struct RobotMeta {
pub elapsed_ms: u64,
}
/// Filter JSON object fields in-place for `--fields` support.
/// Retains only the specified field names on each item in the list array.
pub fn filter_fields(value: &mut serde_json::Value, list_key: &str, fields: &[String]) {
if fields.is_empty() {
return;
}
if let Some(items) = value
.get_mut("data")
.and_then(|d| d.get_mut(list_key))
.and_then(|v| v.as_array_mut())
{
for item in items {
if let Some(obj) = item.as_object_mut() {
obj.retain(|k, _| fields.iter().any(|f| f == k));
}
}
}
}
/// Expand the `minimal` preset into concrete field names.
pub fn expand_fields_preset(fields: &[String], entity: &str) -> Vec<String> {
if fields.len() == 1 && fields[0] == "minimal" {
match entity {
"issues" => ["iid", "title", "state", "updated_at_iso"]
.iter()
.map(|s| (*s).to_string())
.collect(),
"mrs" => ["iid", "title", "state", "updated_at_iso"]
.iter()
.map(|s| (*s).to_string())
.collect(),
"search" => ["document_id", "title", "source_type", "score"]
.iter()
.map(|s| (*s).to_string())
.collect(),
"timeline" => ["timestamp", "type", "entity_iid", "detail"]
.iter()
.map(|s| (*s).to_string())
.collect(),
"who_expert" => ["username", "score"]
.iter()
.map(|s| (*s).to_string())
.collect(),
"who_workload" => ["iid", "title", "state"]
.iter()
.map(|s| (*s).to_string())
.collect(),
"who_active" => ["entity_type", "iid", "title", "participants"]
.iter()
.map(|s| (*s).to_string())
.collect(),
"who_overlap" => ["username", "touch_count"]
.iter()
.map(|s| (*s).to_string())
.collect(),
"who_reviews" => ["name", "count", "percentage"]
.iter()
.map(|s| (*s).to_string())
.collect(),
"notes" => ["id", "author_username", "body", "created_at_iso"]
.iter()
.map(|s| (*s).to_string())
.collect(),
_ => fields.to_vec(),
}
} else {
fields.to_vec()
}
}
/// Strip `response_schema` from every command entry for `--brief` mode.
pub fn strip_schemas(commands: &mut serde_json::Value) {
if let Some(map) = commands.as_object_mut() {
for (_cmd_name, cmd) in map.iter_mut() {
if let Some(obj) = cmd.as_object_mut() {
obj.remove("response_schema");
obj.remove("example_output");
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_expand_fields_preset_notes() {
let fields = vec!["minimal".to_string()];
let expanded = expand_fields_preset(&fields, "notes");
assert_eq!(
expanded,
["id", "author_username", "body", "created_at_iso"]
);
}
#[test]
fn test_expand_fields_preset_passthrough() {
let fields = vec!["id".to_string(), "body".to_string()];
let expanded = expand_fields_preset(&fields, "notes");
assert_eq!(expanded, ["id", "body"]);
}
}