feat: implement lore explain command (bd-9lbr)

Auto-generates structured narratives for issues and MRs from local DB:
- EntitySummary with title, state, author, labels, status
- Key decisions heuristic (correlates state/label changes with nearby notes)
- Activity summary with event counts and time span
- Open threads detection (unresolved discussions)
- Related entities (closing MRs, related issues)
- Timeline of all events in chronological order

7 unit tests, robot-docs entry, autocorrect registry, CLI dispatch wired.
This commit is contained in:
teernisse
2026-02-19 09:26:54 -05:00
parent 1e679a6d72
commit e8ecb561cf
10 changed files with 2352 additions and 79 deletions

View File

@@ -28,8 +28,8 @@ pub fn check_schema_version(conn: &Connection, minimum: i32) -> SchemaCheck {
return SchemaCheck::NoDB;
}
// Read the current version.
match conn.query_row("SELECT version FROM schema_version LIMIT 1", [], |r| {
// Read the highest version (one row per migration).
match conn.query_row("SELECT MAX(version) FROM schema_version", [], |r| {
r.get::<_, i32>(0)
}) {
Ok(version) if version >= minimum => SchemaCheck::Compatible { version },
@@ -65,7 +65,7 @@ pub fn check_data_readiness(conn: &Connection) -> Result<DataReadiness> {
.unwrap_or(false);
let schema_version = conn
.query_row("SELECT version FROM schema_version LIMIT 1", [], |r| {
.query_row("SELECT MAX(version) FROM schema_version", [], |r| {
r.get::<_, i32>(0)
})
.unwrap_or(0);
@@ -247,6 +247,24 @@ mod tests {
assert!(matches!(result, SchemaCheck::NoDB));
}
#[test]
fn test_schema_preflight_multiple_migration_rows() {
let conn = Connection::open_in_memory().unwrap();
conn.execute_batch(
"CREATE TABLE schema_version (version INTEGER, applied_at INTEGER, description TEXT);
INSERT INTO schema_version VALUES (1, 0, 'Initial');
INSERT INTO schema_version VALUES (2, 0, 'Second');
INSERT INTO schema_version VALUES (27, 0, 'Latest');",
)
.unwrap();
let result = check_schema_version(&conn, 20);
assert!(
matches!(result, SchemaCheck::Compatible { version: 27 }),
"should use MAX(version), not first row: {result:?}"
);
}
#[test]
fn test_check_data_readiness_empty() {
let conn = Connection::open_in_memory().unwrap();

View File

@@ -71,6 +71,8 @@ pub struct LaunchOptions {
/// 2. **Data readiness** — check whether the database has any entity data.
/// If empty, start on the Bootstrap screen; otherwise start on Dashboard.
pub fn launch_tui(options: LaunchOptions) -> Result<()> {
let _options = options; // remaining fields (fresh, ascii, etc.) consumed in later phases
// 1. Resolve database path.
let db_path = lore::core::paths::get_db_path(None);
if !db_path.exists() {
@@ -84,7 +86,7 @@ pub fn launch_tui(options: LaunchOptions) -> Result<()> {
// 2. Open DB and run schema preflight.
let db = db::DbManager::open(&db_path)
.with_context(|| format!("opening database at {}", db_path.display()))?;
db.with_reader(|conn| schema_preflight(conn))?;
db.with_reader(schema_preflight)?;
// 3. Check data readiness — bootstrap screen if empty.
let start_on_bootstrap = db.with_reader(|conn| {