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
This commit is contained in:
94
src/main.rs
94
src/main.rs
@@ -11,23 +11,25 @@ use lore::Config;
|
||||
use lore::cli::autocorrect::{self, CorrectionResult};
|
||||
use lore::cli::commands::{
|
||||
IngestDisplay, InitInputs, InitOptions, InitResult, ListFilters, MrListFilters,
|
||||
SearchCliFilters, SyncOptions, TimelineParams, open_issue_in_browser, open_mr_in_browser,
|
||||
print_count, print_count_json, print_doctor_results, print_drift_human, print_drift_json,
|
||||
print_dry_run_preview, print_dry_run_preview_json, print_embed, print_embed_json,
|
||||
print_event_count, print_event_count_json, print_generate_docs, print_generate_docs_json,
|
||||
print_ingest_summary, print_ingest_summary_json, print_list_issues, print_list_issues_json,
|
||||
print_list_mrs, print_list_mrs_json, print_search_results, print_search_results_json,
|
||||
print_show_issue, print_show_issue_json, print_show_mr, print_show_mr_json, print_stats,
|
||||
print_stats_json, print_sync, print_sync_json, print_sync_status, print_sync_status_json,
|
||||
print_timeline, print_timeline_json_with_meta, print_who_human, print_who_json, run_auth_test,
|
||||
run_count, run_count_events, run_doctor, run_drift, run_embed, run_generate_docs, run_ingest,
|
||||
run_ingest_dry_run, run_init, run_list_issues, run_list_mrs, run_search, run_show_issue,
|
||||
run_show_mr, run_stats, run_sync, run_sync_status, run_timeline, run_who,
|
||||
NoteListFilters, SearchCliFilters, SyncOptions, TimelineParams, open_issue_in_browser,
|
||||
open_mr_in_browser, print_count, print_count_json, print_doctor_results, print_drift_human,
|
||||
print_drift_json, print_dry_run_preview, print_dry_run_preview_json, print_embed,
|
||||
print_embed_json, print_event_count, print_event_count_json, print_generate_docs,
|
||||
print_generate_docs_json, print_ingest_summary, print_ingest_summary_json, print_list_issues,
|
||||
print_list_issues_json, print_list_mrs, print_list_mrs_json, print_list_notes,
|
||||
print_list_notes_csv, print_list_notes_json, print_list_notes_jsonl, print_search_results,
|
||||
print_search_results_json, print_show_issue, print_show_issue_json, print_show_mr,
|
||||
print_show_mr_json, print_stats, print_stats_json, print_sync, print_sync_json,
|
||||
print_sync_status, print_sync_status_json, print_timeline, print_timeline_json_with_meta,
|
||||
print_who_human, print_who_json, query_notes, run_auth_test, run_count, run_count_events,
|
||||
run_doctor, run_drift, run_embed, run_generate_docs, run_ingest, run_ingest_dry_run, run_init,
|
||||
run_list_issues, run_list_mrs, run_search, run_show_issue, run_show_mr, run_stats, run_sync,
|
||||
run_sync_status, run_timeline, run_who,
|
||||
};
|
||||
use lore::cli::robot::{RobotMeta, strip_schemas};
|
||||
use lore::cli::{
|
||||
Cli, Commands, CountArgs, EmbedArgs, GenerateDocsArgs, IngestArgs, IssuesArgs, MrsArgs,
|
||||
SearchArgs, StatsArgs, SyncArgs, TimelineArgs, WhoArgs,
|
||||
NotesArgs, SearchArgs, StatsArgs, SyncArgs, TimelineArgs, WhoArgs,
|
||||
};
|
||||
use lore::core::db::{
|
||||
LATEST_SCHEMA_VERSION, create_connection, get_schema_version, run_migrations,
|
||||
@@ -173,6 +175,7 @@ async fn main() {
|
||||
}
|
||||
Some(Commands::Issues(args)) => handle_issues(cli.config.as_deref(), args, robot_mode),
|
||||
Some(Commands::Mrs(args)) => handle_mrs(cli.config.as_deref(), args, robot_mode),
|
||||
Some(Commands::Notes(args)) => handle_notes(cli.config.as_deref(), args, robot_mode),
|
||||
Some(Commands::Search(args)) => {
|
||||
handle_search(cli.config.as_deref(), args, robot_mode).await
|
||||
}
|
||||
@@ -801,6 +804,59 @@ fn handle_mrs(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_notes(
|
||||
config_override: Option<&str>,
|
||||
args: NotesArgs,
|
||||
robot_mode: bool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let start = std::time::Instant::now();
|
||||
let config = Config::load(config_override)?;
|
||||
let db_path = get_db_path(config.storage.db_path.as_deref());
|
||||
let conn = create_connection(&db_path)?;
|
||||
|
||||
let order = if args.asc { "asc" } else { "desc" };
|
||||
let filters = NoteListFilters {
|
||||
limit: args.limit,
|
||||
project: args.project,
|
||||
author: args.author,
|
||||
note_type: args.note_type,
|
||||
include_system: args.include_system,
|
||||
for_issue_iid: args.for_issue,
|
||||
for_mr_iid: args.for_mr,
|
||||
note_id: args.note_id,
|
||||
gitlab_note_id: args.gitlab_note_id,
|
||||
discussion_id: args.discussion_id,
|
||||
since: args.since,
|
||||
until: args.until,
|
||||
path: args.path,
|
||||
contains: args.contains,
|
||||
resolution: args.resolution,
|
||||
sort: args.sort,
|
||||
order: order.to_string(),
|
||||
};
|
||||
|
||||
let result = query_notes(&conn, &filters, &config)?;
|
||||
|
||||
let format = if robot_mode && args.format == "table" {
|
||||
"json"
|
||||
} else {
|
||||
&args.format
|
||||
};
|
||||
|
||||
match format {
|
||||
"json" => print_list_notes_json(
|
||||
&result,
|
||||
start.elapsed().as_millis() as u64,
|
||||
args.fields.as_deref(),
|
||||
),
|
||||
"jsonl" => print_list_notes_jsonl(&result),
|
||||
"csv" => print_list_notes_csv(&result),
|
||||
_ => print_list_notes(&result),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_ingest(
|
||||
config_override: Option<&str>,
|
||||
args: IngestArgs,
|
||||
@@ -2317,6 +2373,17 @@ fn handle_robot_docs(robot_mode: bool, brief: bool) -> Result<(), Box<dyn std::e
|
||||
"active_minimal": ["entity_type", "iid", "title", "participants"]
|
||||
}
|
||||
},
|
||||
"notes": {
|
||||
"description": "List notes from discussions with rich filtering",
|
||||
"flags": ["--limit/-n <N>", "--author/-a <username>", "--note-type <type>", "--contains <text>", "--for-issue <iid>", "--for-mr <iid>", "-p/--project <path>", "--since <period>", "--until <period>", "--path <filepath>", "--resolution <any|unresolved|resolved>", "--sort <created|updated>", "--asc", "--include-system", "--note-id <id>", "--gitlab-note-id <id>", "--discussion-id <id>", "--format <table|json|jsonl|csv>", "--fields <list|minimal>", "--open"],
|
||||
"robot_flags": ["--format json", "--fields minimal"],
|
||||
"example": "lore --robot notes --author jdefting --since 1y --format json --fields minimal",
|
||||
"response_schema": {
|
||||
"ok": "bool",
|
||||
"data": {"notes": "[NoteListRowJson]", "total_count": "int", "showing": "int"},
|
||||
"meta": {"elapsed_ms": "int"}
|
||||
}
|
||||
},
|
||||
"robot-docs": {
|
||||
"description": "This command (agent self-discovery manifest)",
|
||||
"flags": ["--brief"],
|
||||
@@ -2338,6 +2405,7 @@ fn handle_robot_docs(robot_mode: bool, brief: bool) -> Result<(), Box<dyn std::e
|
||||
"search: FTS5 + vector hybrid search across all entities",
|
||||
"who: Expert/workload/reviews analysis per file path or person",
|
||||
"timeline: Chronological event reconstruction across entities",
|
||||
"notes: Rich note listing with author, type, resolution, path, and discussion filters",
|
||||
"stats: Database statistics with document/note/discussion counts",
|
||||
"count: Entity counts with state breakdowns",
|
||||
"embed: Generate vector embeddings for semantic search via Ollama"
|
||||
|
||||
Reference in New Issue
Block a user