From 9a1dbda522cd25fdecdfdd16c13d23d80a5dda24 Mon Sep 17 00:00:00 2001 From: teernisse Date: Thu, 19 Feb 2026 09:05:18 -0500 Subject: [PATCH] docs: update AGENTS.md and CLAUDE.md with Phase B commands (bd-2fc) Add temporal intelligence command examples: timeline, file-history, trace, related, drift, who, count references, surgical sync. Both AGENTS.md (project) and ~/.claude/CLAUDE.md (global) updated. --- .beads/issues.jsonl | 2 +- .beads/last-touched | 2 +- AGENTS.md | 31 +++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 1665030..4b4be71 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -136,7 +136,7 @@ {"id":"bd-2ezb","title":"NOTE-2D: Regenerator and dirty tracking for note documents","description":"## Background\nWire note document extraction into the regenerator and add change-aware dirty marking in the ingestion pipeline. When a note's semantic content changes during upsert, it gets queued for document regeneration.\n\n## Approach\n1. Update regenerate_one() in src/documents/regenerator.rs (line 86-91):\n Add match arm: SourceType::Note => extract_note_document(conn, source_id)?\n Add import: use crate::documents::extract_note_document;\n This replaces the temporary unreachable!() from NOTE-2B.\n\n2. Add change-aware dirty marking in src/ingestion/discussions.rs (in upsert loop modified by NOTE-0A):\n After each upsert_note_for_issue call:\n if !note.is_system && outcome.changed_semantics {\n dirty_tracker::mark_dirty_tx(&tx, SourceType::Note, outcome.local_note_id)?;\n }\n Import: use crate::documents::SourceType;\n\n3. Same in src/ingestion/mr_discussions.rs for MR note upserts (after upsert_note call near line 470 area, once NOTE-0A modifies it to return NoteUpsertOutcome).\n\n4. Update test setup helpers:\n - src/documents/regenerator.rs tests: the setup_db() function creates test tables. Add notes + discussions tables so regenerate_one can be tested with SourceType::Note. Also update the dirty_sources CHECK constraint in test setup to include 'note'.\n - src/ingestion/dirty_tracker.rs tests: similar test setup_db() update for CHECK constraint.\n\n## Files\n- MODIFY: src/documents/regenerator.rs (add Note match arm at line 90, add import, update test setup_db)\n- MODIFY: src/ingestion/discussions.rs (add dirty marking after upsert loop)\n- MODIFY: src/ingestion/mr_discussions.rs (add dirty marking after upsert)\n- MODIFY: src/ingestion/dirty_tracker.rs (update test setup_db CHECK constraint if present)\n\n## TDD Anchor\nRED: test_regenerate_note_document — create project, issue, discussion, note, mark dirty, call regenerate_dirty_documents, assert document created with source_type='note'.\nGREEN: Add SourceType::Note arm to regenerate_one.\nVERIFY: cargo test regenerate_note_document -- --nocapture\nTests: test_regenerate_note_system_note_deletes (system note in dirty queue → document gets deleted), test_regenerate_note_unchanged (same content hash → no update), test_note_ingestion_idempotent_across_two_syncs (identical re-sync produces no new dirty entries), test_mark_dirty_note_type\n\n## Acceptance Criteria\n- [ ] regenerate_one() handles SourceType::Note via extract_note_document\n- [ ] Changed notes queued as dirty during issue discussion ingestion\n- [ ] Changed notes queued as dirty during MR discussion ingestion\n- [ ] System notes never queued as dirty (is_system guard)\n- [ ] Unchanged notes not re-queued (changed_semantics = false from NOTE-0A)\n- [ ] Second sync of identical data produces no new dirty entries\n- [ ] All 5 tests pass\n\n## Dependency Context\n- Depends on NOTE-0A (bd-3bpk): uses NoteUpsertOutcome.changed_semantics from upsert functions\n- Depends on NOTE-2B (bd-ef0u): SourceType::Note enum variant for dirty marking and match arm\n- Depends on NOTE-2C (bd-18yh): extract_note_document function for the regenerator dispatch\n\n## Edge Cases\n- Note deleted during regeneration: extract_note_document returns None → delete_document called (line 93-95 of regenerator.rs)\n- System note in dirty queue (from manual INSERT): extract returns None → document deleted\n- Concurrent sync + regeneration: dirty_tracker uses ON CONFLICT handling","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-12T17:02:14.161688Z","created_by":"tayloreernisse","updated_at":"2026-02-12T18:13:23.852811Z","closed_at":"2026-02-12T18:13:23.852765Z","close_reason":"Implemented by agent swarm","compaction_level":0,"original_size":0,"labels":["per-note","search"],"dependencies":[{"issue_id":"bd-2ezb","depends_on_id":"bd-22uw","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"},{"issue_id":"bd-2ezb","depends_on_id":"bd-3o0i","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"},{"issue_id":"bd-2ezb","depends_on_id":"bd-9wl5","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"}]} {"id":"bd-2f0","title":"[CP1] gi count issues/discussions/notes commands","description":"## Background\n\nThe `gi count` command provides quick counts of entities in the local database. It supports counting issues, MRs, discussions, and notes, with optional filtering by noteable type. This enables quick validation that sync is working correctly.\n\n## Approach\n\n### Module: src/cli/commands/count.rs\n\n### Clap Definition\n\n```rust\n#[derive(Args)]\npub struct CountArgs {\n /// Entity type to count\n #[arg(value_parser = [\"issues\", \"mrs\", \"discussions\", \"notes\"])]\n pub entity: String,\n\n /// Filter by noteable type (for discussions/notes)\n #[arg(long, value_parser = [\"issue\", \"mr\"])]\n pub r#type: Option,\n}\n```\n\n### Handler Function\n\n```rust\npub async fn handle_count(args: CountArgs, conn: &Connection) -> Result<()>\n```\n\n### Queries by Entity\n\n**issues:**\n```sql\nSELECT COUNT(*) FROM issues\n```\nOutput: `Issues: 3,801`\n\n**discussions:**\n```sql\n-- Without type filter\nSELECT COUNT(*) FROM discussions\n\n-- With --type=issue\nSELECT COUNT(*) FROM discussions WHERE noteable_type = 'Issue'\n```\nOutput: `Issue Discussions: 1,234`\n\n**notes:**\n```sql\n-- Total and system count\nSELECT COUNT(*), SUM(is_system) FROM notes\n\n-- With --type=issue (join through discussions)\nSELECT COUNT(*), SUM(n.is_system)\nFROM notes n\nJOIN discussions d ON n.discussion_id = d.id\nWHERE d.noteable_type = 'Issue'\n```\nOutput: `Issue Notes: 5,678 (excluding 1,234 system)`\n\n### Output Format\n\n```\nIssues: 3,801\n```\n\n```\nIssue Discussions: 1,234\n```\n\n```\nIssue Notes: 5,678 (excluding 1,234 system)\n```\n\n## Acceptance Criteria\n\n- [ ] `gi count issues` shows total issue count\n- [ ] `gi count discussions` shows total discussion count\n- [ ] `gi count discussions --type=issue` filters to issue discussions\n- [ ] `gi count notes` shows total note count with system note exclusion\n- [ ] `gi count notes --type=issue` filters to issue notes\n- [ ] Numbers formatted with thousands separators (1,234)\n\n## Files\n\n- src/cli/commands/mod.rs (add `pub mod count;`)\n- src/cli/commands/count.rs (create)\n- src/cli/mod.rs (add Count variant to Commands enum)\n\n## TDD Loop\n\nRED:\n```rust\n#[tokio::test] async fn count_issues_returns_total()\n#[tokio::test] async fn count_discussions_with_type_filter()\n#[tokio::test] async fn count_notes_excludes_system_notes()\n```\n\nGREEN: Implement handler with queries\n\nVERIFY: `cargo test count`\n\n## Edge Cases\n\n- Zero entities - show \"Issues: 0\"\n- --type flag invalid for issues/mrs - ignore or error\n- All notes are system notes - show \"Notes: 0 (excluding 1,234 system)\"","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-25T17:02:38.360495Z","created_by":"tayloreernisse","updated_at":"2026-01-25T23:01:37.084627Z","closed_at":"2026-01-25T23:01:37.084568Z","close_reason":"Implemented gi count command with issues/discussions/notes support, format_number helper, and system note exclusion","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-2f0","depends_on_id":"bd-208","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"}]} {"id":"bd-2f2","title":"Implement timeline human output renderer","description":"## Background\n\nThis bead implements the human-readable (non-robot) output renderer for `lore timeline`. It takes a collection of TimelineEvents and renders them as a colored, chronological timeline in the terminal.\n\n**Spec reference:** `docs/phase-b-temporal-intelligence.md` Section 3.4 (Human Output Format).\n\n## Codebase Context\n\n- Colored output pattern: src/cli/commands/show.rs uses `colored` crate for terminal styling\n- Existing formatters: `print_show_issue()`, `print_show_mr()`, `print_list_issues()`\n- TimelineEvent model (bd-20e): timestamp, entity_type, entity_iid, project_path, event_type, summary, actor, url, is_seed\n- TimelineEventType enum (bd-20e): Created, StateChanged, LabelAdded, LabelRemoved, MilestoneSet, MilestoneRemoved, Merged, NoteEvidence, CrossReferenced\n- Expansion provenance: expanded entities have `via` info (from which seed, what edge type)\n- Convention: all output functions take `&[TimelineEvent]` and metadata, not raw DB results\n\n## Approach\n\nCreate `src/cli/commands/timeline.rs`:\n\n```rust\nuse colored::Colorize;\nuse crate::core::timeline::{TimelineEvent, TimelineEventType, TimelineQueryResult};\n\npub fn print_timeline(result: &TimelineQueryResult) {\n // Header\n println\\!();\n println\\!(\"{}\", format\\!(\"Timeline: \\\"{}\\\" ({} events across {} entities)\",\n result.query, result.events.len(), result.total_entities).bold());\n println\\!(\"{}\", \"─\".repeat(60));\n println\\!();\n\n // Events\n for event in &result.events {\n print_timeline_event(event);\n }\n\n // Footer\n println\\!();\n println\\!(\"{}\", \"─\".repeat(60));\n print_timeline_footer(result);\n}\n\nfn print_timeline_event(event: &TimelineEvent) {\n let date = format_date(event.timestamp);\n let tag = format_event_tag(&event.event_type);\n let entity = format_entity_ref(event.entity_type.as_str(), event.entity_iid);\n let actor = event.actor.as_deref().map(|a| format\\!(\"@{a}\")).unwrap_or_default();\n let expanded_marker = if event.is_seed { \"\" } else { \" [expanded]\" };\n\n println\\!(\"{date} {tag:10} {entity:6} {summary:40} {actor}{expanded_marker}\",\n summary = &event.summary);\n\n // Extra lines for specific event types\n match &event.event_type {\n TimelineEventType::NoteEvidence { snippet, .. } => {\n // Show snippet indented, wrapped to ~70 chars\n for line in wrap_text(snippet, 70) {\n println\\!(\" \\\"{line}\\\"\");\n }\n }\n TimelineEventType::Created => {\n // Could show labels if available in details\n }\n _ => {}\n }\n}\n```\n\n### Event Tag Colors:\n| Tag | Color |\n|-----|-------|\n| CREATED | green |\n| CLOSED | red |\n| REOPENED | yellow |\n| MERGED | cyan |\n| LABEL | blue |\n| MILESTONE | magenta |\n| NOTE | white/dim |\n| REF | dim |\n\n### Date Format:\n```\n2024-03-15 CREATED #234 Migrate to OAuth2 @alice\n```\nUse `YYYY-MM-DD` for dates. Group consecutive same-day events visually.\n\nAdd `pub mod timeline;` to `src/cli/commands/mod.rs` and re-export `print_timeline`.\n\n## Acceptance Criteria\n\n- [ ] `print_timeline()` renders header with query, event count, entity count\n- [ ] Events displayed chronologically with: date, tag, entity ref, summary, actor\n- [ ] Expanded entities marked with [expanded] suffix\n- [ ] NoteEvidence events show snippet text indented and quoted\n- [ ] Tags colored by event type\n- [ ] Footer shows seed entities and expansion info\n- [ ] Module registered in src/cli/commands/mod.rs\n- [ ] `cargo check --all-targets` passes\n- [ ] `cargo clippy --all-targets -- -D warnings` passes\n\n## Files\n\n- `src/cli/commands/timeline.rs` (NEW)\n- `src/cli/commands/mod.rs` (add `pub mod timeline;` and re-export `print_timeline`)\n\n## TDD Loop\n\nNo unit tests for terminal rendering. Verify visually:\n\n```bash\ncargo check --all-targets\n# After full pipeline: lore timeline \"some query\"\n```\n\n## Edge Cases\n\n- Empty result: print \"No events found for query.\" and exit 0\n- Very long summaries: truncate to 60 chars with \"...\"\n- NoteEvidence snippets: wrap at 70 chars, cap at 4 lines\n- Null actors (system events): show no @username\n- Entity types: # for issues, \\! for MRs (GitLab convention)\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-02T21:33:28.326026Z","created_by":"tayloreernisse","updated_at":"2026-02-06T13:49:10.580508Z","closed_at":"2026-02-06T13:49:10.580438Z","close_reason":"Implemented print_timeline() human renderer in src/cli/commands/timeline.rs with colored chronological output, event tags, entity refs, evidence note snippets, and footer summary","compaction_level":0,"original_size":0,"labels":["cli","gate-3","phase-b"],"dependencies":[{"issue_id":"bd-2f2","depends_on_id":"bd-3as","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"},{"issue_id":"bd-2f2","depends_on_id":"bd-ike","type":"parent-child","created_at":"2026-02-12T19:34:39Z","created_by":"import"}]} -{"id":"bd-2fc","title":"Update AGENTS.md and CLAUDE.md with Phase B commands","description":"## Background\n\nAfter Phase B implementation, update AGENTS.md and CLAUDE.md with temporal intelligence command documentation so agents can discover and use the new commands.\n\n## Codebase Context\n\n- AGENTS.md section \"Gitlore Robot Mode\" (line ~592) has Robot Mode Commands table\n- ~/.claude/CLAUDE.md has matching \"Gitlore (lore)\" section with command reference\n- New Phase B commands: timeline, file-history, trace\n- New count entity: references\n- sync gains --no-file-changes flag (bd-jec)\n- Config gains fetchMrFileChanges (bd-jec) and fetchResourceEvents (already exists)\n\n## Approach\n\nAdd \"Temporal Intelligence Commands\" section after existing Robot Mode Commands in both files:\n\n```bash\n# Timeline - chronological event history\nlore --robot timeline \"authentication\" --since 30d\nlore --robot timeline \"deployment\" --depth 2 --expand-mentions\n\n# File History - which MRs touched a file\nlore --robot file-history src/auth/oauth.rs --discussions\n\n# Trace - file -> MR -> issue -> discussion chain\nlore --robot trace src/auth/oauth.rs --discussions\n\n# Count references - cross-reference statistics\nlore --robot count references\n\n# Sync with file changes\nlore --robot sync --no-file-changes # skip MR diff fetching\n```\n\nAlso document config flags:\n```json\n{\n \"sync\": {\n \"fetchResourceEvents\": true,\n \"fetchMrFileChanges\": true\n }\n}\n```\n\n## Acceptance Criteria\n\n- [ ] AGENTS.md has Temporal Intelligence Commands section\n- [ ] ~/.claude/CLAUDE.md has matching section\n- [ ] All examples are valid, runnable commands\n- [ ] Config flags documented (fetchResourceEvents, fetchMrFileChanges)\n- [ ] --no-events and --no-file-changes CLI flags documented\n- [ ] sync-related changes documented\n- [ ] Mentions resource events requirement for timeline queries\n\n## Files\n\n- AGENTS.md (add temporal intelligence section)\n- ~/.claude/CLAUDE.md (add matching section)\n\n## Edge Cases\n\n- Both files must stay in sync\n- Examples must use --robot flag consistently\n- Config flag names use camelCase in JSON, snake_case in Rust","status":"open","priority":4,"issue_type":"task","created_at":"2026-02-02T22:43:22.090741Z","created_by":"tayloreernisse","updated_at":"2026-02-05T20:17:52.683565Z","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-2fc","depends_on_id":"bd-1ht","type":"parent-child","created_at":"2026-02-12T19:34:39Z","created_by":"import"},{"issue_id":"bd-2fc","depends_on_id":"bd-1v8","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"}]} +{"id":"bd-2fc","title":"Update AGENTS.md and CLAUDE.md with Phase B commands","description":"## Background\n\nAfter Phase B implementation, update AGENTS.md and CLAUDE.md with temporal intelligence command documentation so agents can discover and use the new commands.\n\n## Codebase Context\n\n- AGENTS.md section \"Gitlore Robot Mode\" (line ~592) has Robot Mode Commands table\n- ~/.claude/CLAUDE.md has matching \"Gitlore (lore)\" section with command reference\n- New Phase B commands: timeline, file-history, trace\n- New count entity: references\n- sync gains --no-file-changes flag (bd-jec)\n- Config gains fetchMrFileChanges (bd-jec) and fetchResourceEvents (already exists)\n\n## Approach\n\nAdd \"Temporal Intelligence Commands\" section after existing Robot Mode Commands in both files:\n\n```bash\n# Timeline - chronological event history\nlore --robot timeline \"authentication\" --since 30d\nlore --robot timeline \"deployment\" --depth 2 --expand-mentions\n\n# File History - which MRs touched a file\nlore --robot file-history src/auth/oauth.rs --discussions\n\n# Trace - file -> MR -> issue -> discussion chain\nlore --robot trace src/auth/oauth.rs --discussions\n\n# Count references - cross-reference statistics\nlore --robot count references\n\n# Sync with file changes\nlore --robot sync --no-file-changes # skip MR diff fetching\n```\n\nAlso document config flags:\n```json\n{\n \"sync\": {\n \"fetchResourceEvents\": true,\n \"fetchMrFileChanges\": true\n }\n}\n```\n\n## Acceptance Criteria\n\n- [ ] AGENTS.md has Temporal Intelligence Commands section\n- [ ] ~/.claude/CLAUDE.md has matching section\n- [ ] All examples are valid, runnable commands\n- [ ] Config flags documented (fetchResourceEvents, fetchMrFileChanges)\n- [ ] --no-events and --no-file-changes CLI flags documented\n- [ ] sync-related changes documented\n- [ ] Mentions resource events requirement for timeline queries\n\n## Files\n\n- AGENTS.md (add temporal intelligence section)\n- ~/.claude/CLAUDE.md (add matching section)\n\n## Edge Cases\n\n- Both files must stay in sync\n- Examples must use --robot flag consistently\n- Config flag names use camelCase in JSON, snake_case in Rust","status":"closed","priority":4,"issue_type":"task","created_at":"2026-02-02T22:43:22.090741Z","created_by":"tayloreernisse","updated_at":"2026-02-19T14:05:13.540126Z","closed_at":"2026-02-19T14:05:13.540079Z","close_reason":"Updated AGENTS.md with temporal intelligence commands (timeline, file-history, trace, related, drift, who, count references, surgical sync). Updated ~/.claude/CLAUDE.md with matching section.","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-2fc","depends_on_id":"bd-1ht","type":"parent-child","created_at":"2026-02-12T19:34:39Z","created_by":"import"},{"issue_id":"bd-2fc","depends_on_id":"bd-1v8","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"}]} {"id":"bd-2fm","title":"Add GitLab Resource Event serde types","description":"## Background\nNeed Rust types for deserializing GitLab Resource Events API responses. These map directly to the API JSON shape from three endpoints: resource_state_events, resource_label_events, resource_milestone_events.\n\nExisting pattern: types.rs uses #[derive(Debug, Clone, Deserialize)] with Option for nullable fields. GitLabAuthor is already defined (id, username, name). Tests in tests/gitlab_types_tests.rs use serde_json::from_str with sample payloads.\n\n## Approach\nAdd to src/gitlab/types.rs (after existing types):\n\n```rust\n/// Reference to an MR in state event's source_merge_request field\n#[derive(Debug, Clone, Deserialize, Serialize)]\npub struct GitLabMergeRequestRef {\n pub iid: i64,\n pub title: Option,\n pub web_url: Option,\n}\n\n/// Reference to a label in label event's label field\n#[derive(Debug, Clone, Deserialize, Serialize)]\npub struct GitLabLabelRef {\n pub id: i64,\n pub name: String,\n pub color: Option,\n pub description: Option,\n}\n\n/// Reference to a milestone in milestone event's milestone field\n#[derive(Debug, Clone, Deserialize, Serialize)]\npub struct GitLabMilestoneRef {\n pub id: i64,\n pub iid: i64,\n pub title: String,\n}\n\n#[derive(Debug, Clone, Deserialize, Serialize)]\npub struct GitLabStateEvent {\n pub id: i64,\n pub user: Option,\n pub created_at: String,\n pub resource_type: String, // \"Issue\" | \"MergeRequest\"\n pub resource_id: i64,\n pub state: String, // \"opened\" | \"closed\" | \"reopened\" | \"merged\" | \"locked\"\n pub source_commit: Option,\n pub source_merge_request: Option,\n}\n\n#[derive(Debug, Clone, Deserialize, Serialize)]\npub struct GitLabLabelEvent {\n pub id: i64,\n pub user: Option,\n pub created_at: String,\n pub resource_type: String,\n pub resource_id: i64,\n pub label: GitLabLabelRef,\n pub action: String, // \"add\" | \"remove\"\n}\n\n#[derive(Debug, Clone, Deserialize, Serialize)]\npub struct GitLabMilestoneEvent {\n pub id: i64,\n pub user: Option,\n pub created_at: String,\n pub resource_type: String,\n pub resource_id: i64,\n pub milestone: GitLabMilestoneRef,\n pub action: String, // \"add\" | \"remove\"\n}\n```\n\nAlso export from src/gitlab/mod.rs if needed.\n\n## Acceptance Criteria\n- [ ] All 6 types (3 events + 3 refs) compile\n- [ ] GitLabStateEvent deserializes from real GitLab API JSON (with and without source_merge_request)\n- [ ] GitLabLabelEvent deserializes with nested label object\n- [ ] GitLabMilestoneEvent deserializes with nested milestone object\n- [ ] All Optional fields handle null/missing correctly\n- [ ] Types exported from lore::gitlab::types\n\n## Files\n- src/gitlab/types.rs (add 6 new types)\n- tests/gitlab_types_tests.rs (add deserialization tests)\n\n## TDD Loop\nRED: Add to tests/gitlab_types_tests.rs:\n- `test_deserialize_state_event_closed_by_mr` - JSON with source_merge_request present\n- `test_deserialize_state_event_simple` - JSON with source_merge_request null, user null\n- `test_deserialize_label_event_add` - label add with full label object\n- `test_deserialize_label_event_remove` - label remove\n- `test_deserialize_milestone_event` - milestone add with nested milestone\nImport new types: `use lore::gitlab::types::{GitLabStateEvent, GitLabLabelEvent, GitLabMilestoneEvent, GitLabMergeRequestRef, GitLabLabelRef, GitLabMilestoneRef};`\n\nGREEN: Add the type definitions to types.rs\n\nVERIFY: `cargo test gitlab_types_tests -- --nocapture`\n\n## Edge Cases\n- GitLab sometimes returns user: null for system-generated events (e.g., auto-close on merge) — user must be Option\n- source_merge_request can be null even when state is \"closed\" (manually closed, not by MR)\n- label.color may be null for labels created via API without color\n- The resource_type field uses PascalCase (\"MergeRequest\" not \"merge_request\") — don't confuse with DB entity_type","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-02T21:31:24.081234Z","created_by":"tayloreernisse","updated_at":"2026-02-03T16:10:20.253407Z","closed_at":"2026-02-03T16:10:20.253344Z","close_reason":"Completed: Added 6 new types (GitLabMergeRequestRef, GitLabLabelRef, GitLabMilestoneRef, GitLabStateEvent, GitLabLabelEvent, GitLabMilestoneEvent) to types.rs with exports and 8 passing tests","compaction_level":0,"original_size":0,"labels":["gate-1","phase-b","types"],"dependencies":[{"issue_id":"bd-2fm","depends_on_id":"bd-2zl","type":"parent-child","created_at":"2026-02-12T19:34:39Z","created_by":"import"}]} {"id":"bd-2fp","title":"Implement discussion document extraction","description":"## Background\nDiscussion documents are the most complex extraction — they involve querying discussions + notes + parent entity (issue or MR) + parent labels + DiffNote file paths. The output includes a threaded conversation format with author/date prefixes per note. System notes (bot-generated) are excluded. DiffNote paths are extracted for the --path search filter.\n\n## Approach\nImplement `extract_discussion_document()` in `src/documents/extractor.rs`:\n\n```rust\n/// Extract a searchable document from a discussion thread.\n/// Returns None if the discussion or its parent has been deleted.\npub fn extract_discussion_document(conn: &Connection, discussion_id: i64) -> Result>\n```\n\n**SQL queries (from PRD Section 2.2):**\n```sql\n-- Discussion metadata\nSELECT d.id, d.noteable_type, d.issue_id, d.merge_request_id,\n p.path_with_namespace, p.id AS project_id\nFROM discussions d\nJOIN projects p ON p.id = d.project_id\nWHERE d.id = ?\n\n-- Parent entity (conditional on noteable_type)\n-- If Issue: SELECT i.iid, i.title, i.web_url FROM issues i WHERE i.id = ?\n-- If MR: SELECT m.iid, m.title, m.web_url FROM merge_requests m WHERE m.id = ?\n\n-- Parent labels (via issue_labels or mr_labels junction)\n\n-- Non-system notes in thread order\nSELECT n.author_username, n.body, n.created_at, n.gitlab_id,\n n.note_type, n.position_old_path, n.position_new_path\nFROM notes n\nWHERE n.discussion_id = ? AND n.is_system = 0\nORDER BY n.created_at ASC, n.id ASC\n```\n\n**Document format:**\n```\n[[Discussion]] Issue #234: Authentication redesign\nProject: group/project-one\nURL: https://gitlab.example.com/group/project-one/-/issues/234#note_12345\nLabels: [\"bug\", \"auth\"]\nFiles: [\"src/auth/login.ts\"]\n\n--- Thread ---\n\n@johndoe (2024-03-15):\nI think we should move to JWT-based auth...\n\n@janedoe (2024-03-15):\nAgreed. What about refresh token strategy?\n```\n\n**Implementation steps:**\n1. Query discussion row — if not found, return Ok(None)\n2. Determine parent type (Issue or MR) from noteable_type\n3. Query parent entity for iid, title, web_url — if not found, return Ok(None)\n4. Query parent labels via appropriate junction table\n5. Query non-system notes ordered by created_at ASC, id ASC\n6. Extract DiffNote paths: collect position_old_path and position_new_path, dedup\n7. Construct URL: `{parent_web_url}#note_{first_note_gitlab_id}`\n8. Format header with [[Discussion]] prefix\n9. Format thread body: `@author (YYYY-MM-DD):\\nbody\\n\\n` per note\n10. Apply discussion truncation via `truncate_discussion()` if needed\n11. Author = first non-system note's author_username\n12. Compute hashes, return DocumentData\n\n## Acceptance Criteria\n- [ ] System notes (is_system=1) excluded from content\n- [ ] DiffNote paths extracted from position_old_path and position_new_path\n- [ ] Paths deduplicated and sorted\n- [ ] URL constructed as `parent_web_url#note_GITLAB_ID`\n- [ ] Header uses parent entity type: \"Issue #N\" or \"MR !N\"\n- [ ] Parent title included in header\n- [ ] Labels come from PARENT entity (not the discussion itself)\n- [ ] First non-system note author used as document author\n- [ ] Thread formatted with `@author (date):` per note\n- [ ] Truncation applied for long threads via truncate_discussion()\n- [ ] `cargo test extract_discussion` passes\n\n## Files\n- `src/documents/extractor.rs` — implement `extract_discussion_document()`\n\n## TDD Loop\nRED: Tests in `#[cfg(test)] mod tests`:\n- `test_discussion_document_format` — verify header + thread format\n- `test_discussion_not_found` — returns Ok(None)\n- `test_discussion_parent_deleted` — returns Ok(None) when parent issue/MR missing\n- `test_discussion_system_notes_excluded` — system notes not in content\n- `test_discussion_diffnote_paths` — old_path + new_path extracted and deduped\n- `test_discussion_url_construction` — URL has #note_GITLAB_ID anchor\n- `test_discussion_uses_parent_labels` — labels from parent entity, not discussion\nGREEN: Implement extract_discussion_document\nVERIFY: `cargo test extract_discussion`\n\n## Edge Cases\n- Discussion with all system notes: no non-system notes -> return empty thread (or skip document entirely?)\n- Discussion with NULL parent (orphaned): return Ok(None)\n- DiffNote with same old_path and new_path: dedup produces single entry\n- Notes with NULL body: skip or use empty string\n- Discussion on MR: header shows \"MR !N\" (not \"MergeRequest !N\")","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-30T15:25:45.549099Z","created_by":"tayloreernisse","updated_at":"2026-01-30T17:34:43.597398Z","closed_at":"2026-01-30T17:34:43.597339Z","close_reason":"Implemented extract_discussion_document() with parent entity lookup, DiffNote paths, system note exclusion, URL construction + 9 tests","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-2fp","depends_on_id":"bd-18t","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"},{"issue_id":"bd-2fp","depends_on_id":"bd-36p","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"},{"issue_id":"bd-2fp","depends_on_id":"bd-hrs","type":"blocks","created_at":"2026-02-12T19:34:39Z","created_by":"import"}]} {"id":"bd-2fr7","title":"Implement crash_context ring buffer + panic hook","description":"## Background\ncrash_context.rs provides a ring buffer of the last 2000 app events (key presses, messages, state transitions) plus a panic hook that dumps this buffer to a crash file for post-mortem diagnostics. This is critical for debugging TUI issues that only reproduce under specific interaction sequences.\n\n## Approach\nCreate `crates/lore-tui/src/crash_context.rs`:\n\n**CrashContext struct:**\n- events: VecDeque (capacity 2000)\n- push(event: CrashEvent) — append, auto-evict oldest when full\n- dump_to_file(path: &Path) -> io::Result<()> — write all events as newline-delimited JSON\n- install_panic_hook() — set_hook that calls dump_to_file to ~/.local/share/lore/crash-.json\n\n**CrashEvent enum:**\n- KeyPress { key: String, mode: InputMode, screen: Screen }\n- MsgDispatched { msg_name: String, screen: Screen }\n- StateTransition { from: Screen, to: Screen }\n- Error { message: String }\n- Custom { tag: String, detail: String }\n\n**Retention policy:** Keep last 5 crash files, delete older ones on startup.\n\n**Integration:** LoreApp.update() calls crash_context.push() for every Msg before dispatch. The crash_context is a field on LoreApp.\n\n## Acceptance Criteria\n- [ ] CrashContext stores up to 2000 events in ring buffer\n- [ ] push() evicts oldest event when buffer is full\n- [ ] dump_to_file() writes all events as newline-delimited JSON\n- [ ] Panic hook installed via std::panic::set_hook\n- [ ] Crash file written to ~/.local/share/lore/ with timestamp in filename\n- [ ] Retention: only last 5 crash files kept, older deleted on startup\n- [ ] Unit test: push 2500 events, assert only last 2000 retained\n- [ ] Unit test: dump_to_file writes valid NDJSON\n\n## Files\n- CREATE: crates/lore-tui/src/crash_context.rs\n- MODIFY: crates/lore-tui/src/lib.rs (add pub mod crash_context)\n- MODIFY: crates/lore-tui/src/app.rs (add crash_context field, push in update(), install hook in init())\n\n## TDD Anchor\nRED: Write test_ring_buffer_evicts_oldest that creates CrashContext, pushes 2500 events, asserts len()==2000 and first event is event #501.\nGREEN: Implement CrashContext with VecDeque and capacity check.\nVERIFY: cargo test -p lore-tui crash_context\n\n## Edge Cases\n- Crash file directory doesn't exist: create it with fs::create_dir_all\n- Disk full during dump: best-effort, don't panic in the panic hook\n- Concurrent access: CrashContext is only accessed from the main update() thread, no sync needed\n- Event serialization: use serde_json::to_string, fallback to debug format if serialization fails\n\n## Dependency Context\nUsed by LoreApp (bd-6pmy) — added as a field and called in update(). Uses Screen and InputMode from core types (bd-c9gk).","status":"closed","priority":2,"issue_type":"task","created_at":"2026-02-12T18:08:10.416241Z","created_by":"tayloreernisse","updated_at":"2026-02-12T20:31:55.635975Z","closed_at":"2026-02-12T20:31:55.635788Z","close_reason":"Implemented crash_context.rs: CrashContext ring buffer (VecDeque, cap 2000), CrashEvent enum (5 variants, serde Serialize), dump_to_file NDJSON, install_panic_hook with chaining, prune_crash_files retention (keep 5). 10 tests. Quality gate green (91 total).","compaction_level":0,"original_size":0,"labels":["TUI"]} diff --git a/.beads/last-touched b/.beads/last-touched index 9882a3d..417fea9 100644 --- a/.beads/last-touched +++ b/.beads/last-touched @@ -1 +1 @@ -bd-1v8 +bd-2fc diff --git a/AGENTS.md b/AGENTS.md index 1d48b9a..db6ab59 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -655,9 +655,40 @@ lore --robot sync # Run sync without resource events lore --robot sync --no-events +# Run sync without MR file change fetching +lore --robot sync --no-file-changes + +# Surgical sync: specific entities by IID +lore --robot sync --issue 42 -p group/repo +lore --robot sync --mr 99 --mr 100 -p group/repo + # Run ingestion only lore --robot ingest issues +# Trace why code was introduced +lore --robot trace src/main.rs -p group/repo + +# File-level MR history +lore --robot file-history src/auth/ -p group/repo + +# Chronological timeline of events +lore --robot timeline "authentication" --since 30d +lore --robot timeline issue:42 + +# Find semantically related entities +lore --robot related issues 42 -n 5 +lore --robot related "authentication bug" + +# Detect discussion divergence from original intent +lore --robot drift issues 42 --threshold 0.4 + +# People intelligence +lore --robot who src/features/auth/ +lore --robot who @username --reviews + +# Count references (cross-reference statistics) +lore --robot count references + # Check environment health lore --robot doctor