diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 7f86b28..1665030 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -85,7 +85,7 @@ {"id":"bd-1uc","title":"Implement DB upsert functions for resource events","description":"## Background\nNeed to store fetched resource events into the three event tables created by migration 011. The existing DB pattern uses rusqlite prepared statements with named parameters. Timestamps from GitLab are ISO 8601 strings that need conversion to ms epoch UTC (matching the existing time.rs parse_datetime_to_ms function).\n\n## Approach\nCreate src/core/events_db.rs (new module) with three upsert functions:\n\n```rust\nuse rusqlite::Connection;\nuse super::error::Result;\n\n/// Upsert state events for an entity.\n/// Uses INSERT OR REPLACE keyed on UNIQUE(gitlab_id, project_id).\npub fn upsert_state_events(\n conn: &Connection,\n project_id: i64, // local DB project id\n entity_type: &str, // \"issue\" | \"merge_request\"\n entity_local_id: i64, // local DB id of the issue/MR\n events: &[GitLabStateEvent],\n) -> Result\n\n/// Upsert label events for an entity.\npub fn upsert_label_events(\n conn: &Connection,\n project_id: i64,\n entity_type: &str,\n entity_local_id: i64,\n events: &[GitLabLabelEvent],\n) -> Result\n\n/// Upsert milestone events for an entity.\npub fn upsert_milestone_events(\n conn: &Connection,\n project_id: i64,\n entity_type: &str,\n entity_local_id: i64,\n events: &[GitLabMilestoneEvent],\n) -> Result\n```\n\nEach function:\n1. Prepares INSERT OR REPLACE statement\n2. For each event, maps GitLab types to DB columns:\n - `actor_gitlab_id` = event.user.map(|u| u.id)\n - `actor_username` = event.user.map(|u| u.username.clone())\n - `created_at` = parse_datetime_to_ms(&event.created_at)?\n - Set issue_id or merge_request_id based on entity_type\n3. Returns count of upserted rows\n4. Wraps in a savepoint for atomicity per entity\n\nRegister module in src/core/mod.rs:\n```rust\npub mod events_db;\n```\n\n## Acceptance Criteria\n- [ ] All three upsert functions compile and handle all event fields\n- [ ] Upserts are idempotent (re-inserting same event doesn't duplicate)\n- [ ] Timestamps converted to ms epoch UTC via parse_datetime_to_ms\n- [ ] actor_gitlab_id and actor_username populated from event.user (handles None)\n- [ ] entity_type correctly maps to issue_id/merge_request_id (other is NULL)\n- [ ] source_merge_request_id populated for state events (iid from source_merge_request)\n- [ ] source_commit populated for state events\n- [ ] label_name populated for label events\n- [ ] milestone_title and milestone_id populated for milestone events\n- [ ] Returns upserted count\n\n## Files\n- src/core/events_db.rs (new)\n- src/core/mod.rs (add `pub mod events_db;`)\n\n## TDD Loop\nRED: tests/events_db_tests.rs (new):\n- `test_upsert_state_events_basic` - insert 3 events, verify count and data\n- `test_upsert_state_events_idempotent` - insert same events twice, verify no duplicates\n- `test_upsert_label_events_with_actor` - verify actor fields populated\n- `test_upsert_milestone_events_null_user` - verify user: null doesn't crash\n- `test_upsert_state_events_entity_exclusivity` - verify only one of issue_id/merge_request_id set\n\nSetup: create_test_db() helper that applies migrations 001-011, inserts a test project + issue + MR.\n\nGREEN: Implement the three functions\n\nVERIFY: `cargo test events_db -- --nocapture`\n\n## Edge Cases\n- parse_datetime_to_ms must handle GitLab's format: \"2024-03-15T10:30:00.000Z\" and \"2024-03-15T10:30:00.000+00:00\"\n- INSERT OR REPLACE will fire CASCADE deletes if there are FK references to these rows — currently no other table references event rows, so this is safe\n- entity_type must be validated (\"issue\" or \"merge_request\") — panic or error on invalid\n- source_merge_request field contains an MR ref object, not an ID — extract .iid for DB column","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-02T21:31:57.242549Z","created_by":"tayloreernisse","updated_at":"2026-02-03T16:19:14.169437Z","closed_at":"2026-02-03T16:19:14.169233Z","close_reason":"Implemented upsert_state_events, upsert_label_events, upsert_milestone_events, count_events in src/core/events_db.rs. Uses savepoints for atomicity, LoreError::Database via ? operator for clean error handling.","compaction_level":0,"original_size":0,"labels":["db","gate-1","phase-b"],"dependencies":[{"issue_id":"bd-1uc","depends_on_id":"bd-2zl","type":"parent-child","created_at":"2026-02-12T19:34:39Z","created_by":"import"},{"issue_id":"bd-1uc","depends_on_id":"bd-hu3","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"}]} {"id":"bd-1up1","title":"Implement File History screen (per-file MR timeline with rename tracking)","description":"## Background\nThe File History screen shows which MRs touched a file over time, with rename-aware tracking and optional DiffNote discussion snippets. It wraps run_file_history() from src/cli/commands/file_history.rs (added in v0.8.1) in a TUI view. While Trace answers \"why was this code introduced?\", File History answers \"what happened to this file?\" — a chronological MR timeline.\n\nThe core query resolves rename chains via BFS (resolve_rename_chain from src/core/file_history.rs), finds all MRs with mr_file_changes entries matching any renamed path, and optionally fetches DiffNote discussions on those paths.\n\n## Data Shapes (from src/cli/commands/file_history.rs)\n\n```rust\npub struct FileHistoryResult {\n pub path: String,\n pub rename_chain: Vec, // resolved paths via BFS\n pub renames_followed: bool,\n pub merge_requests: Vec,\n pub discussions: Vec,\n pub total_mrs: usize,\n pub paths_searched: usize,\n}\n\npub struct FileHistoryMr {\n pub iid: i64,\n pub title: String,\n pub state: String, // merged/opened/closed\n pub author_username: String,\n pub change_type: String, // added/modified/deleted/renamed\n pub merged_at_iso: Option,\n pub updated_at_iso: String,\n pub merge_commit_sha: Option,\n pub web_url: Option,\n}\n\npub struct FileDiscussion {\n pub discussion_id: String,\n pub author_username: String,\n pub body_snippet: String,\n pub path: String,\n pub created_at_iso: String,\n}\n```\n\nrun_file_history() signature (src/cli/commands/file_history.rs):\n```rust\npub fn run_file_history(\n config: &Config, // used only for DB path — bd-1f5b will extract query-only version\n path: &str,\n project: Option<&str>,\n no_follow_renames: bool,\n merged_only: bool,\n include_discussions: bool,\n limit: usize,\n) -> Result\n```\n\nAfter bd-1f5b extracts the query logic, the TUI will call a Connection-based variant:\n```rust\npub fn query_file_history(\n conn: &Connection,\n project_id: Option,\n path: &str,\n follow_renames: bool,\n merged_only: bool,\n include_discussions: bool,\n limit: usize,\n) -> Result\n```\n\n## Approach\n\n**Screen enum** (message.rs):\nAdd Screen::FileHistory variant (no parameters). Label: \"File History\". Breadcrumb: \"File History\".\n\n**Path autocomplete**: Same mechanism as Trace screen — query DISTINCT new_path from mr_file_changes. Share the known_paths cache with Trace if both are loaded, or each screen maintains its own (simpler).\n\n**State** (state/file_history.rs):\n```rust\n#[derive(Debug, Default)]\npub struct FileHistoryState {\n pub path_input: String,\n pub path_focused: bool,\n pub result: Option,\n pub selected_mr_index: usize,\n pub follow_renames: bool, // default true\n pub merged_only: bool, // default false\n pub show_discussions: bool, // default false\n pub scroll_offset: u16,\n pub known_paths: Vec, // autocomplete cache\n pub autocomplete_matches: Vec,\n pub autocomplete_index: usize,\n}\n```\n\n**Action** (action.rs):\n- fetch_file_history(conn, project_id, path, follow_renames, merged_only, show_discussions, limit) -> Result: calls query_file_history from file_history module (after bd-1f5b extraction)\n- fetch_known_paths(conn, project_id): shared with Trace screen (same query)\n\n**View** (view/file_history.rs):\n- Top: path input with autocomplete dropdown + toggle indicators [renames: on] [merged: off] [discussions: off]\n- If renames followed: rename chain breadcrumb (path_a -> path_b -> path_c) in dimmed text\n- Summary line: \"N merge requests across M paths\"\n- Main area: chronological MR list (sorted by updated_at descending):\n - Each row: MR state icon + !iid + title + @author + change_type tag + date\n - If show_discussions: inline discussion snippets beneath relevant MRs (indented, dimmed, author + date + body_snippet)\n- Footer: \"showing N of M\" when total_mrs > limit\n- Keyboard:\n - j/k: scroll MR list\n - Enter: navigate to MrDetail(EntityKey::mr(project_id, iid))\n - /: focus path input\n - Tab: cycle autocomplete suggestions when path focused\n - r: toggle follow_renames (re-fetches)\n - m: toggle merged_only (re-fetches)\n - d: toggle show_discussions (re-fetches)\n - q: back\n\n**Contextual entry points** (wired from other screens):\n- MR Detail: h on a file path opens File History pre-filled with that path\n- Expert mode (Who screen): when viewing a file path's experts, h opens File History for that path\n- Requires other screens to expose selected_file_path() -> Option\n\n## Acceptance Criteria\n- [ ] Screen::FileHistory added to message.rs Screen enum with label and breadcrumb\n- [ ] FileHistoryState struct with all fields, Default impl\n- [ ] Path input with autocomplete dropdown from mr_file_changes (same mechanism as Trace)\n- [ ] Rename chain displayed as breadcrumb when renames_followed is true\n- [ ] Chronological MR list with state icons (merged/opened/closed) and change_type tags\n- [ ] Enter on MR navigates to MrDetail(EntityKey::mr(project_id, iid))\n- [ ] r toggles follow_renames, m toggles merged_only, d toggles show_discussions — all re-fetch\n- [ ] Discussion snippets shown inline beneath MRs when toggled on\n- [ ] Summary line showing \"N merge requests across M paths\"\n- [ ] Footer truncation indicator when total_mrs > display limit\n- [ ] Empty state: \"No MRs found for this file\" with hint \"Run 'lore sync --fetch-mr-file-changes' to populate\"\n- [ ] Contextual navigation: h on file path in MR Detail opens File History pre-filled\n- [ ] Registered in command palette (label \"File History\", keywords [\"history\", \"file\", \"changes\"])\n- [ ] AppState.has_text_focus() updated to include file_history.path_focused\n- [ ] AppState.blur_text_focus() updated to include file_history.path_focused = false\n\n## Files\n- MODIFY: crates/lore-tui/src/message.rs (add Screen::FileHistory variant + label)\n- CREATE: crates/lore-tui/src/state/file_history.rs (FileHistoryState struct + Default)\n- MODIFY: crates/lore-tui/src/state/mod.rs (pub mod file_history, pub use FileHistoryState, add to AppState, update has_text_focus/blur_text_focus)\n- MODIFY: crates/lore-tui/src/action.rs (add fetch_file_history, share fetch_known_paths with Trace)\n- CREATE: crates/lore-tui/src/view/file_history.rs (render_file_history fn)\n- MODIFY: crates/lore-tui/src/view/mod.rs (add Screen::FileHistory dispatch arm)\n\n## TDD Anchor\nRED: Write test_fetch_file_history_returns_mrs in action tests. Setup: in-memory DB, insert project, MR (state=\"merged\", merged_at set), mr_file_changes row (new_path=\"src/lib.rs\", change_type=\"modified\"). Call fetch_file_history(conn, Some(project_id), \"src/lib.rs\", true, false, false, 50). Assert: result.merge_requests.len() == 1, result.merge_requests[0].iid matches.\nGREEN: Implement fetch_file_history calling query_file_history.\nVERIFY: cargo test -p lore-tui file_history -- --nocapture\n\nAdditional tests:\n- test_file_history_empty: path \"nonexistent.rs\" returns empty merge_requests\n- test_file_history_rename_chain: insert rename A->B, query A, assert rename_chain=[\"A\",\"B\"] and MRs touching B are included\n- test_file_history_merged_only: merged_only=true excludes opened/closed MRs\n- test_file_history_discussions: show_discussions=true populates discussions vec with DiffNote snippets\n- test_file_history_limit: insert 10 MRs, limit=5, assert merge_requests.len()==5 and total_mrs==10\n- test_autocomplete: shared with Trace tests\n\n## Edge Cases\n- File never modified by any MR: empty state with helpful message and sync hint\n- Rename chain with cycles: BFS visited set in resolve_rename_chain prevents infinite loop\n- Very long file paths: truncate from left in list view (...path/to/file.rs)\n- Hundreds of MRs for a single file: default limit 50, footer shows total count\n- Discussion body_snippet may contain markdown/code — render as plain text, no parsing\n- No mr_file_changes data at all: hint that sync needs --fetch-mr-file-changes (config.sync.fetch_mr_file_changes)\n- Project scope: if global_scope.project_id is set, pass it to query and autocomplete\n\n## Dependency Context\n- bd-1f5b (blocks): Extracts query_file_history(conn, ...) from run_file_history(config, ...) in src/cli/commands/file_history.rs. The current function opens its own DB connection from Config — TUI needs a Connection-based variant since it manages its own connection.\n- src/core/file_history.rs: resolve_rename_chain() used by query_file_history internally. TUI does not call it directly.\n- FileHistoryResult, FileHistoryMr, FileDiscussion: currently defined in src/cli/commands/file_history.rs — bd-1f5b should move these to core or make them importable.\n- Navigation: uses NavigationStack.push(Screen::MrDetail(key)) from crates/lore-tui/src/navigation.rs.\n- AppState composition: FileHistoryState added as field in AppState (state/mod.rs ~line 154-174). has_text_focus/blur_text_focus at lines 194-207 must include file_history.path_focused.\n- Autocomplete: fetch_known_paths query identical to Trace screen — consider extracting to shared helper in action.rs.\n- Contextual entry: requires MrDetailState to expose selected file path. Deferred if MR Detail not yet built.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-18T18:14:13.179338Z","created_by":"tayloreernisse","updated_at":"2026-02-19T03:47:22.812185Z","closed_at":"2026-02-19T03:47:22.811968Z","close_reason":"File History screen complete: state, action, view, full wiring. 579 TUI tests pass.","compaction_level":0,"original_size":0,"labels":["TUI"],"dependencies":[{"issue_id":"bd-1up1","depends_on_id":"bd-1f5b","type":"blocks","created_at":"2026-02-18T18:14:33.412864Z","created_by":"tayloreernisse"},{"issue_id":"bd-1up1","depends_on_id":"bd-nwux","type":"parent-child","created_at":"2026-02-18T18:14:13.180816Z","created_by":"tayloreernisse"}]} {"id":"bd-1ut","title":"[CP0] Final validation - tests, lint, typecheck","description":"## Background\n\nFinal validation ensures everything works together before marking CP0 complete. This is the integration gate - all unit tests, integration tests, lint, and type checking must pass. Manual smoke tests verify the full user experience.\n\nReference: docs/prd/checkpoint-0.md sections \"Definition of Done\", \"Manual Smoke Tests\"\n\n## Approach\n\n**Automated checks:**\n```bash\n# All tests pass\nnpm run test\n\n# TypeScript strict mode\nnpm run build # or: npx tsc --noEmit\n\n# ESLint with no errors\nnpm run lint\n```\n\n**Manual smoke tests (from PRD table):**\n\n| Command | Expected | Pass Criteria |\n|---------|----------|---------------|\n| `gi --help` | Command list | Shows all commands |\n| `gi version` | Version number | Shows installed version |\n| `gi init` | Interactive prompts | Creates valid config |\n| `gi init` (config exists) | Confirmation prompt | Warns before overwriting |\n| `gi init --force` | No prompt | Overwrites without asking |\n| `gi auth-test` | `Authenticated as @username` | Shows GitLab username |\n| `GITLAB_TOKEN=invalid gi auth-test` | Error message | Non-zero exit, clear error |\n| `gi doctor` | Status table | All required checks pass |\n| `gi doctor --json` | JSON object | Valid JSON, `success: true` |\n| `gi backup` | Backup path | Creates timestamped backup |\n| `gi sync-status` | No runs message | Stub output works |\n\n**Definition of Done gate items:**\n- [ ] `gi init` writes config to XDG path and validates projects against GitLab\n- [ ] `gi auth-test` succeeds with real PAT\n- [ ] `gi doctor` reports DB ok + GitLab ok\n- [ ] DB migrations apply; WAL + FK enabled; busy_timeout + synchronous set\n- [ ] App lock mechanism works (concurrent runs blocked)\n- [ ] All unit tests pass\n- [ ] All integration tests pass (mocked)\n- [ ] ESLint passes with no errors\n- [ ] TypeScript compiles with strict mode\n\n## Acceptance Criteria\n\n- [ ] `npm run test` exits 0 (all tests pass)\n- [ ] `npm run build` exits 0 (TypeScript compiles)\n- [ ] `npm run lint` exits 0 (no ESLint errors)\n- [ ] All 11 manual smoke tests pass\n- [ ] All 9 Definition of Done gate items verified\n\n## Files\n\nNo new files created. This bead verifies existing work.\n\n## TDD Loop\n\nThis IS the final verification step:\n\n```bash\n# Automated\nnpm run test\nnpm run build\nnpm run lint\n\n# Manual (requires GITLAB_TOKEN set with valid token)\ngi --help\ngi version\ngi init # go through setup\ngi auth-test\ngi doctor\ngi doctor --json | jq .success # should output true\ngi backup\ngi sync-status\ngi reset --confirm\ngi init # re-setup\n```\n\n## Edge Cases\n\n- Test coverage should be reasonable (aim for 80%+ on core modules)\n- Integration tests may flake on CI - check MSW setup\n- Manual tests require real GitLab token - document in README\n- ESLint may warn vs error - only errors block\n- TypeScript noImplicitAny catches missed types","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-24T16:09:52.078907Z","created_by":"tayloreernisse","updated_at":"2026-01-25T03:37:51.858558Z","closed_at":"2026-01-25T03:37:51.858474Z","close_reason":"done","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-1ut","depends_on_id":"bd-1cb","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"},{"issue_id":"bd-1ut","depends_on_id":"bd-1gu","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"},{"issue_id":"bd-1ut","depends_on_id":"bd-1kh","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"},{"issue_id":"bd-1ut","depends_on_id":"bd-38e","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"},{"issue_id":"bd-1ut","depends_on_id":"bd-3kj","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"}]} -{"id":"bd-1v8","title":"Update robot-docs manifest with Phase B commands","description":"## Background\n\nThe robot-docs manifest is the agent self-discovery mechanism. It must include all Phase B commands so agents can discover temporal intelligence features.\n\n## Codebase Context\n\n- handle_robot_docs() in src/main.rs (line ~1646) returns JSON with commands, exit_codes, workflows, aliases, clap_error_codes\n- Currently 18 commands documented in the manifest\n- VALID_COMMANDS array in src/main.rs (line ~448): [\"issues\", \"mrs\", \"search\", \"sync\", \"ingest\", \"count\", \"status\", \"auth\", \"doctor\", \"version\", \"init\", \"stats\", \"generate-docs\", \"embed\", \"migrate\", \"health\", \"robot-docs\", \"completions\"]\n- Phase B adds 3 new commands: timeline, file-history, trace\n- count gains new entity: \"references\" (bd-2ez)\n- Existing workflows: first_setup, daily_sync, search, pre_flight\n\n## Approach\n\n### 1. Add commands to handle_robot_docs() JSON:\n\n```json\n\"timeline\": {\n \"description\": \"Chronological timeline of events matching a keyword query\",\n \"flags\": [\"\", \"-p \", \"--since \", \"--depth \", \"--expand-mentions\", \"-n \"],\n \"example\": \"lore --robot timeline 'authentication' --since 30d\"\n},\n\"file-history\": {\n \"description\": \"Which MRs touched a file, with rename chain resolution\",\n \"flags\": [\"\", \"-p \", \"--discussions\", \"--no-follow-renames\", \"--merged\", \"-n \"],\n \"example\": \"lore --robot file-history src/auth/oauth.rs\"\n},\n\"trace\": {\n \"description\": \"Trace file -> MR -> issue -> discussions decision chain\",\n \"flags\": [\"\", \"-p \", \"--discussions\", \"--no-follow-renames\", \"-n \"],\n \"example\": \"lore --robot trace src/auth/oauth.rs\"\n}\n```\n\n### 2. Update count command to mention \"references\" entity\n\n### 3. Add temporal_intelligence workflow:\n```json\n\"temporal_intelligence\": {\n \"description\": \"Query temporal data about project history\",\n \"steps\": [\n \"lore sync (ensure events fetched with fetchResourceEvents=true)\",\n \"lore timeline '' for chronological event history\",\n \"lore file-history for file-level MR history\",\n \"lore trace for file -> MR -> issue -> discussion chain\"\n ]\n}\n```\n\n### 4. Add timeline, file-history, trace to VALID_COMMANDS array\n\n## Acceptance Criteria\n\n- [ ] robot-docs includes timeline, file-history, trace commands\n- [ ] count references documented\n- [ ] temporal_intelligence workflow present\n- [ ] VALID_COMMANDS includes all 3 new commands\n- [ ] Examples are valid, runnable commands\n- [ ] cargo check --all-targets passes\n- [ ] cargo clippy --all-targets -- -D warnings passes\n\n## Files\n\n- src/main.rs (update handle_robot_docs + VALID_COMMANDS array)\n\n## TDD Loop\n\nVERIFY: lore robot-docs | jq '.data.commands.timeline'\nVERIFY: lore robot-docs | jq '.data.workflows.temporal_intelligence'","status":"in_progress","priority":3,"issue_type":"task","created_at":"2026-02-02T22:43:07.859092Z","created_by":"tayloreernisse","updated_at":"2026-02-19T14:01:25.024024Z","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-1v8","depends_on_id":"bd-1ht","type":"parent-child","created_at":"2026-02-12T19:34:39Z","created_by":"import"},{"issue_id":"bd-1v8","depends_on_id":"bd-2ez","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"},{"issue_id":"bd-1v8","depends_on_id":"bd-2n4","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"}]} +{"id":"bd-1v8","title":"Update robot-docs manifest with Phase B commands","description":"## Background\n\nThe robot-docs manifest is the agent self-discovery mechanism. It must include all Phase B commands so agents can discover temporal intelligence features.\n\n## Codebase Context\n\n- handle_robot_docs() in src/main.rs (line ~1646) returns JSON with commands, exit_codes, workflows, aliases, clap_error_codes\n- Currently 18 commands documented in the manifest\n- VALID_COMMANDS array in src/main.rs (line ~448): [\"issues\", \"mrs\", \"search\", \"sync\", \"ingest\", \"count\", \"status\", \"auth\", \"doctor\", \"version\", \"init\", \"stats\", \"generate-docs\", \"embed\", \"migrate\", \"health\", \"robot-docs\", \"completions\"]\n- Phase B adds 3 new commands: timeline, file-history, trace\n- count gains new entity: \"references\" (bd-2ez)\n- Existing workflows: first_setup, daily_sync, search, pre_flight\n\n## Approach\n\n### 1. Add commands to handle_robot_docs() JSON:\n\n```json\n\"timeline\": {\n \"description\": \"Chronological timeline of events matching a keyword query\",\n \"flags\": [\"\", \"-p \", \"--since \", \"--depth \", \"--expand-mentions\", \"-n \"],\n \"example\": \"lore --robot timeline 'authentication' --since 30d\"\n},\n\"file-history\": {\n \"description\": \"Which MRs touched a file, with rename chain resolution\",\n \"flags\": [\"\", \"-p \", \"--discussions\", \"--no-follow-renames\", \"--merged\", \"-n \"],\n \"example\": \"lore --robot file-history src/auth/oauth.rs\"\n},\n\"trace\": {\n \"description\": \"Trace file -> MR -> issue -> discussions decision chain\",\n \"flags\": [\"\", \"-p \", \"--discussions\", \"--no-follow-renames\", \"-n \"],\n \"example\": \"lore --robot trace src/auth/oauth.rs\"\n}\n```\n\n### 2. Update count command to mention \"references\" entity\n\n### 3. Add temporal_intelligence workflow:\n```json\n\"temporal_intelligence\": {\n \"description\": \"Query temporal data about project history\",\n \"steps\": [\n \"lore sync (ensure events fetched with fetchResourceEvents=true)\",\n \"lore timeline '' for chronological event history\",\n \"lore file-history for file-level MR history\",\n \"lore trace for file -> MR -> issue -> discussion chain\"\n ]\n}\n```\n\n### 4. Add timeline, file-history, trace to VALID_COMMANDS array\n\n## Acceptance Criteria\n\n- [ ] robot-docs includes timeline, file-history, trace commands\n- [ ] count references documented\n- [ ] temporal_intelligence workflow present\n- [ ] VALID_COMMANDS includes all 3 new commands\n- [ ] Examples are valid, runnable commands\n- [ ] cargo check --all-targets passes\n- [ ] cargo clippy --all-targets -- -D warnings passes\n\n## Files\n\n- src/main.rs (update handle_robot_docs + VALID_COMMANDS array)\n\n## TDD Loop\n\nVERIFY: lore robot-docs | jq '.data.commands.timeline'\nVERIFY: lore robot-docs | jq '.data.workflows.temporal_intelligence'","status":"closed","priority":3,"issue_type":"task","created_at":"2026-02-02T22:43:07.859092Z","created_by":"tayloreernisse","updated_at":"2026-02-19T14:03:17.986523Z","closed_at":"2026-02-19T14:03:17.986468Z","close_reason":"Updated robot-docs manifest: added related command, updated count to include references entity with schema, expanded temporal_intelligence workflow with file-history and trace, updated lore_exclusive list. All quality gates pass.","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-1v8","depends_on_id":"bd-1ht","type":"parent-child","created_at":"2026-02-12T19:34:39Z","created_by":"import"},{"issue_id":"bd-1v8","depends_on_id":"bd-2ez","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"},{"issue_id":"bd-1v8","depends_on_id":"bd-2n4","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"}]} {"id":"bd-1v8t","title":"Add WorkItemStatus type and SyncConfig toggle","description":"## Background\nThe GraphQL status response returns name, category, color, and iconName fields. We need a Rust struct that deserializes this directly. Category is stored as raw Option (not an enum) because GitLab 18.5+ supports custom statuses with arbitrary category values. We also need a config toggle so users can disable status enrichment.\n\n## Approach\nAdd WorkItemStatus to the existing types module. Add fetch_work_item_status to the existing SyncConfig with default_true() helper. Also add WorkItemStatus to pub use re-exports in src/gitlab/mod.rs.\n\n## Files\n- src/gitlab/types.rs (add struct after GitLabMergeRequest, before #[cfg(test)])\n- src/core/config.rs (add field to SyncConfig struct + Default impl)\n- src/gitlab/mod.rs (add WorkItemStatus to pub use)\n\n## Implementation\n\nIn src/gitlab/types.rs (needs Serialize, Deserialize derives already in scope):\n #[derive(Debug, Clone, Serialize, Deserialize)]\n pub struct WorkItemStatus {\n pub name: String,\n pub category: Option,\n pub color: Option,\n #[serde(rename = \"iconName\")]\n pub icon_name: Option,\n }\n\nIn src/core/config.rs SyncConfig struct (after fetch_mr_file_changes):\n #[serde(rename = \"fetchWorkItemStatus\", default = \"default_true\")]\n pub fetch_work_item_status: bool,\n\nIn impl Default for SyncConfig (after fetch_mr_file_changes: true):\n fetch_work_item_status: true,\n\n## Acceptance Criteria\n- [ ] WorkItemStatus deserializes: {\"name\":\"In progress\",\"category\":\"IN_PROGRESS\",\"color\":\"#1f75cb\",\"iconName\":\"status-in-progress\"}\n- [ ] Optional fields: {\"name\":\"To do\"} -> category/color/icon_name are None\n- [ ] Unknown category: {\"name\":\"Custom\",\"category\":\"SOME_FUTURE_VALUE\"} -> Ok\n- [ ] Null category: {\"name\":\"In progress\",\"category\":null} -> None\n- [ ] SyncConfig::default().fetch_work_item_status == true\n- [ ] JSON without fetchWorkItemStatus key -> defaults true\n- [ ] cargo check --all-targets passes\n\n## TDD Loop\nRED: test_work_item_status_deserialize, test_work_item_status_optional_fields, test_work_item_status_unknown_category, test_work_item_status_null_category, test_config_fetch_work_item_status_default_true, test_config_deserialize_without_key\nGREEN: Add struct + config field\nVERIFY: cargo test test_work_item_status && cargo test test_config\n\n## Edge Cases\n- serde rename \"iconName\" -> icon_name (camelCase in GraphQL)\n- Category is Option, NOT an enum\n- Config key is camelCase \"fetchWorkItemStatus\" matching existing convention","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-11T06:41:42.790001Z","created_by":"tayloreernisse","updated_at":"2026-02-11T07:21:33.416990Z","closed_at":"2026-02-11T07:21:33.416950Z","close_reason":"Implemented by agent swarm — all quality gates pass (595 tests, 0 failures)","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-1v8t","depends_on_id":"bd-2y79","type":"parent-child","created_at":"2026-02-12T19:34:39Z","created_by":"import"}]} {"id":"bd-1v9m","title":"Implement AppState composition + LoadState + ScreenIntent","description":"## Background\nAppState is the top-level state composition — each field corresponds to one screen. State is preserved when navigating away (never cleared on pop). LoadState enables stale-while-revalidate: screens show last data during refresh with a spinner. ScreenIntent is the pure return type from state handlers — they never launch async tasks directly.\n\n## Approach\nCreate crates/lore-tui/src/state/mod.rs:\n- AppState struct: dashboard (DashboardState), issue_list (IssueListState), issue_detail (IssueDetailState), mr_list (MrListState), mr_detail (MrDetailState), search (SearchState), timeline (TimelineState), who (WhoState), sync (SyncState), command_palette (CommandPaletteState), global_scope (ScopeContext), load_state (ScreenLoadStateMap), error_toast (Option), show_help (bool), terminal_size ((u16, u16))\n- LoadState enum: Idle, LoadingInitial, Refreshing, Error(String)\n- ScreenLoadStateMap: wraps HashMap, get()/set()/any_loading()\n- AppState methods: set_loading(), set_error(), clear_error(), has_text_focus(), blur_text_focus(), delegate_text_event(), interpret_screen_key(), handle_screen_msg()\n- ScreenIntent enum: None, Navigate(Screen), RequeryNeeded(Screen)\n- handle_screen_msg() matches Msg variants and returns ScreenIntent (NEVER Cmd::task)\n\nCreate stub per-screen state files (just Default-derivable structs):\n- state/dashboard.rs, issue_list.rs, issue_detail.rs, mr_list.rs, mr_detail.rs, search.rs, timeline.rs, who.rs, sync.rs, command_palette.rs\n\n## Acceptance Criteria\n- [ ] AppState derives Default and compiles with all screen state fields\n- [ ] LoadState has Idle, LoadingInitial, Refreshing, Error variants\n- [ ] ScreenLoadStateMap::get() returns Idle for untracked screens\n- [ ] ScreenLoadStateMap::any_loading() returns true when any screen is loading\n- [ ] has_text_focus() checks all filter/query focused flags\n- [ ] blur_text_focus() resets all focus flags\n- [ ] handle_screen_msg() returns ScreenIntent, never Cmd::task\n- [ ] ScreenIntent::RequeryNeeded signals that LoreApp should dispatch supervised query\n\n## Files\n- CREATE: crates/lore-tui/src/state/mod.rs\n- CREATE: crates/lore-tui/src/state/dashboard.rs (stub)\n- CREATE: crates/lore-tui/src/state/issue_list.rs (stub)\n- CREATE: crates/lore-tui/src/state/issue_detail.rs (stub)\n- CREATE: crates/lore-tui/src/state/mr_list.rs (stub)\n- CREATE: crates/lore-tui/src/state/mr_detail.rs (stub)\n- CREATE: crates/lore-tui/src/state/search.rs (stub)\n- CREATE: crates/lore-tui/src/state/timeline.rs (stub)\n- CREATE: crates/lore-tui/src/state/who.rs (stub)\n- CREATE: crates/lore-tui/src/state/sync.rs (stub)\n- CREATE: crates/lore-tui/src/state/command_palette.rs (stub)\n\n## TDD Anchor\nRED: Write test_load_state_default_idle that creates ScreenLoadStateMap, asserts get(&Screen::Dashboard) returns Idle.\nGREEN: Implement ScreenLoadStateMap with HashMap defaulting to Idle.\nVERIFY: cargo test --manifest-path crates/lore-tui/Cargo.toml test_load_state\n\n## Edge Cases\n- LoadState::set() removes Idle entries from the map to prevent unbounded growth\n- Screen::IssueDetail(key) comparison for HashMap: requires Screen to impl Hash+Eq or use ScreenKind discriminant\n- has_text_focus() must be kept in sync as new screens add text inputs","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-12T16:56:42.023482Z","created_by":"tayloreernisse","updated_at":"2026-02-12T20:35:46.811462Z","closed_at":"2026-02-12T20:35:46.811406Z","close_reason":"Implemented state/ module: AppState (11 screen fields + cross-cutting), LoadState (4 variants), ScreenLoadStateMap (auto-prune Idle), ScreenIntent (None/Navigate/RequeryNeeded), ScopeContext, 10 per-screen state stubs. 12 tests. Quality gate green (114 total).","compaction_level":0,"original_size":0,"labels":["TUI"],"dependencies":[{"issue_id":"bd-1v9m","depends_on_id":"bd-c9gk","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"}]} {"id":"bd-1vti","title":"Write decay and scoring example-based tests (TDD)","description":"## Background\nAll implementation beads (bd-1soz through bd-11mg) include their own inline TDD tests. This bead is the integration verification: run the full test suite and confirm everything works together with no regressions.\n\n## Approach\nRun cargo test and verify:\n1. All NEW tests pass (31 tests across implementation beads)\n2. All EXISTING tests pass unchanged (existing who tests, config tests, etc.)\n3. No test interference (--test-threads=1 mode)\n4. All tests in who.rs test module compile and run cleanly\n\nTest count by bead:\n- bd-1soz: 2 (test_half_life_decay_math, test_score_monotonicity_by_age)\n- bd-2w1p: 3 (test_config_validation_rejects_zero_half_life, _absurd_half_life, _nan_multiplier)\n- bd-18dn: 2 (test_path_normalization_handles_dot_and_double_slash, _preserves_prefix_semantics)\n- bd-1hoq: 1 (test_expert_sql_returns_expected_signal_rows)\n- bd-1h3f: 2 (test_old_path_probe_exact_and_prefix, test_suffix_probe_uses_old_path_sources)\n- bd-13q8: 13 (decay integration + invariant tests)\n- bd-11mg: 8 (CLI flag tests: explain_score, as_of, excluded_usernames, etc.)\nTotal: 2+3+2+1+2+13+8 = 31 new tests\n\nThis is NOT a code-writing bead — it is a verification checkpoint.\n\n## Acceptance Criteria\n- [ ] cargo test -p lore passes (all tests green)\n- [ ] cargo test -p lore -- --test-threads=1 passes (no test interference)\n- [ ] No existing test assertions were changed (only callsite signatures updated in bd-13q8 and ScoringConfig literals in bd-1b50)\n- [ ] Total test count: existing + 31 new = all pass\n\n## TDD Loop\nN/A — this bead verifies, does not write code.\nVERIFY: cargo test -p lore\n\n## Files\nNone modified — read-only verification.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-09T17:00:29.453420Z","created_by":"tayloreernisse","updated_at":"2026-02-12T20:43:04.414775Z","closed_at":"2026-02-12T20:43:04.414735Z","close_reason":"Implemented by time-decay swarm: 3 agents, 12 tasks, 621 tests passing, all quality gates green","compaction_level":0,"original_size":0,"labels":["scoring","test"],"dependencies":[{"issue_id":"bd-1vti","depends_on_id":"bd-11mg","type":"blocks","created_at":"2026-02-18T17:42:00Z","created_by":"import"},{"issue_id":"bd-1vti","depends_on_id":"bd-18dn","type":"blocks","created_at":"2026-02-18T17:42:00Z","created_by":"import"},{"issue_id":"bd-1vti","depends_on_id":"bd-1b50","type":"blocks","created_at":"2026-02-18T17:42:00Z","created_by":"import"},{"issue_id":"bd-1vti","depends_on_id":"bd-1h3f","type":"blocks","created_at":"2026-02-18T17:42:00Z","created_by":"import"},{"issue_id":"bd-1vti","depends_on_id":"bd-1soz","type":"blocks","created_at":"2026-02-18T17:42:00Z","created_by":"import"},{"issue_id":"bd-1vti","depends_on_id":"bd-2w1p","type":"blocks","created_at":"2026-02-18T17:42:00Z","created_by":"import"},{"issue_id":"bd-1vti","depends_on_id":"bd-2yu5","type":"blocks","created_at":"2026-02-18T17:42:00Z","created_by":"import"}]} diff --git a/.beads/last-touched b/.beads/last-touched index e276029..9882a3d 100644 --- a/.beads/last-touched +++ b/.beads/last-touched @@ -1 +1 @@ -bd-3jqx +bd-1v8