Introduce src/cli/render.rs as the single source of truth for all terminal
output styling and formatting utilities. Key components:
- LoreRenderer: global singleton initialized once at startup, resolving
color mode (Auto/Always/Never) against TTY state and NO_COLOR env var.
This fixes lipgloss's limitation of hardcoded TrueColor rendering by
gating all style application through a colors_on() check.
- Theme: semantic style constants (success/warning/error/info/accent,
entity refs, state colors, structural styles) that return plain
Style::new() when colors are disabled. Replaces ad-hoc console::style()
calls scattered across 15+ command modules.
- Shared formatting utilities consolidated from duplicated implementations:
format_relative_time (was in list.rs and who.rs), format_number (was in
count.rs and sync_status.rs), truncate (was truncate_with_ellipsis in
list.rs and truncate_summary in timeline.rs), format_labels, format_date,
wrap_indent, section_divider.
- LoreTable: lightweight table renderer replacing comfy-table with simple
column alignment (Left/Right/Center), adaptive terminal width, and
NO_COLOR-safe output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch from comfy-table to the lipgloss Rust port for terminal styling.
lipgloss provides a composable Style API better suited to our new semantic
theming approach (Theme::success(), Theme::error(), etc.) where we apply
styles to individual text spans rather than constructing styled table cells.
The comfy-table dependency was only used by the list command's human output
and is no longer needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two related improvements to agent ergonomics in main.rs:
1. suggest_similar_command now matches against aliases (issue->issues,
mr->mrs, find->search, stat->stats, note->notes, etc.) and provides
contextual usage examples via a new command_example() helper, so
agents get actionable recovery hints like "Did you mean 'lore mrs'?
Example: lore --robot mrs -n 10" instead of just the command name.
2. robot-docs now includes an error_tolerance section documenting every
auto-correction the CLI performs: types (single_dash_long_flag,
case_normalization, flag_prefix, fuzzy_flag, subcommand_alias,
value_normalization, value_fuzzy, prefix_matching), examples, and
mode behavior (threshold differences). Also expands the aliases
section with command_aliases and pre_clap_aliases maps for complete
agent self-discovery.
Together these ensure agents can programmatically discover and recover
from any CLI input error without human intervention.
Three-phase pipeline replacing the single-pass correction:
- Phase A: Subcommand alias correction — handles forms clap can't
express (merge_requests, mergerequests, robotdocs, generatedocs,
gen-docs, etc.) via case-insensitive alias map lookup.
- Phase B: Per-arg flag corrections — adds unambiguous prefix expansion
(--proj -> --project) alongside existing single-dash, case, and fuzzy
rules. New FlagPrefix rule with 0.95 confidence.
- Phase C: Enum value normalization — auto-corrects casing, prefixes,
and typos for flags with known valid values. Handles both --flag value
and --flag=value forms. Respects POSIX -- option terminator.
Changes strict/robot mode from disabling fuzzy matching entirely to using
a higher threshold (0.9 vs 0.8), still catching obvious typos like
--projct while avoiding speculative corrections that mislead agents.
New CorrectionRule variants: SubcommandAlias, ValueNormalization,
ValueFuzzy, FlagPrefix. Each has a corresponding teaching note.
Comprehensive test coverage for all new correction types including
subcommand aliases, value normalization (case, prefix, fuzzy, eq-form),
flag prefix (ambiguous rejection, eq-value preservation), and updated
strict mode behavior.
- Fix MENTIONED_RE/CLOSED_BY_RE to match real GitLab format
('mentioned in issue #N' / 'mentioned in merge request !N')
- Add GITLAB_URL_RE + parse_url_refs() for full URL extraction
- Add extract_refs_from_descriptions() -> source_method='description_parse'
- Add extract_refs_from_user_notes() -> source_method='note_parse'
- Wire both into orchestrator after system note extraction
- 36 tests: regex fix, URL parsing, integration, idempotency
Adds issue:N / i:N / mr:N / m:N query syntax to bypass hybrid search
and seed the timeline directly from a known entity. All discussions for
the entity are gathered without needing Ollama.
- parse_timeline_query() detects entity-direct patterns
- resolve_entity_by_iid() resolves IID to EntityRef with ambiguity handling
- seed_timeline_direct() gathers all discussions for the entity
- 20 new tests (5 resolve, 6 direct seed, 9 parse)
- Updated CLI help text and robot-docs manifest
Moves the conn.prepare() call for fetching discussion notes outside the
per-discussion loop in collect_discussion_threads(). The SQL is identical
for every iteration, so preparing it once and rebinding parameters avoids
redundant statement compilation on each matched discussion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
FTS5 boolean operators (AND, OR, NOT, NEAR) are case-sensitive uppercase
keywords that must appear unquoted in the query string. Previously, the
user-friendly query builder would double-quote every token, causing
queries like "switch AND health" to search for the literal word "AND"
instead of using it as a boolean conjunction.
Adds a FTS5_OPERATORS constant and checks each token against it before
quoting, allowing natural boolean search syntax to work as expected.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds numbered stage spinners ([1/3], [2/3], [3/3]) to the timeline
pipeline stages (seed, expand, collect) so users see activity during
longer queries. TimelineParams gains a robot_mode field to suppress
spinners in JSON output mode.
Adds a [1/1] spinner to the search command for consistency, using the
shared stage_spinner from cli/progress.
Also refactors wrap_snippet() to delegate to wrap_text() with a 4-line
cap, eliminating the duplicated word-wrapping logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Moves stage_spinner() from a private function in sync.rs to a pub function
in cli/progress.rs so it can be reused by the timeline and search commands.
The function creates a numbered spinner (e.g. [1/3]) for pipeline stages,
returning a hidden no-op bar in robot mode to keep caller code path-uniform.
sync.rs now imports from crate::cli::progress::stage_spinner instead of
defining its own copy. Adds unit tests for robot mode (hidden bar), human
mode (prefix/message properties), and prefix formatting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements the downstream consumption of matched discussions from the seed
phase, completing the discussion thread feature across collect, CLI, and
integration tests.
Collect phase (timeline_collect.rs):
- New collect_discussion_threads() function assembles full threads by
querying notes for each matched discussion_id, filtering out system notes
(is_system = 0), ordering chronologically, and capping at THREAD_MAX_NOTES
with a synthetic "[N more notes not shown]" summary note
- build_entity_lookup() creates a (type, id) -> (iid, path) map from seed
and expanded entities to provide display metadata for thread events
- Thread timestamp is set to the first note's created_at for correct
chronological interleaving with other timeline events
- collect_events() gains a matched_discussions parameter; threads are
collected after entity events and before evidence note merging
CLI rendering (cli/commands/timeline.rs):
- Human mode: threads render with box-drawing borders, bold @author tags,
date-stamped notes, and word-wrapped bodies (60 char width)
- Robot mode: DiscussionThread serializes as discussion_thread kind with
note_count, full notes array (note_id, author, body, ISO created_at)
- THREAD tag in yellow for human event tag styling
- TimelineMeta gains discussion_threads_included count
Tests:
- 8 new collect tests: basic thread assembly, system note filtering, empty
thread skipping, body truncation to THREAD_NOTE_MAX_CHARS, note cap with
synthetic summary, timestamp from first note, chronological sort position,
and deduplication of duplicate discussion_ids
- Integration tests updated for new collect_events signature
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduces the foundation for full discussion thread support in the
timeline pipeline. Adds three new domain types to timeline.rs:
- ThreadNote: individual note within a thread (id, author, body, timestamp)
- MatchedDiscussion: tracks discussions matched during seeding with their
parent entity (issue or MR) for downstream collection
- DiscussionThread variant on TimelineEventType: carries a full thread of
notes, sorted between NoteEvidence and CrossReferenced
Moves truncate_to_chars() from timeline_seed.rs to timeline.rs as pub(crate)
for reuse by the collect phase. Adds THREAD_NOTE_MAX_CHARS (2000) and
THREAD_MAX_NOTES (50) constants.
Upgrades the seed SQL in resolve_documents_to_entities() to resolve note
documents to their parent discussion via an additional LEFT JOIN chain
(notes -> discussions), using COALESCE to unify the entity resolution path
for both discussion and note source types. SeedResult gains a
matched_discussions field that captures deduplicated discussion matches.
Tests cover: discussion matching from discussion docs, note-to-parent
resolution, deduplication of same discussion across multiple docs, and
correct parent entity type (issue vs MR).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace FTS-only seed entity discovery with hybrid search (FTS + vector
via RRF), using the same search_hybrid infrastructure as the search
command. Falls back gracefully to FTS-only when Ollama is unavailable.
Changes:
- seed_timeline() now accepts OllamaClient, delegates to search_hybrid
- New resolve_documents_to_entities() replaces find_seed_entities()
- SeedResult gains search_mode field tracking actual mode used
- TimelineResult carries search_mode through to JSON renderer
- run_timeline wires up OllamaClient from config
- handle_timeline made async for the hybrid search await
- Tests updated for new function signatures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract shared path resolution logic from who.rs into a new
core::path_resolver module for cross-module reuse. Functions moved:
escape_like, normalize_repo_path, PathQuery, SuffixResult,
build_path_query, suffix_probe. Duplicate escape_like copies removed
from list.rs, project.rs, and filters.rs — all now import from
path_resolver.
Additionally fixes two bugs in query_expert_details() and
query_overlap() where only position_new_path was checked (missing
old_path matches for renamed files) and state filter excluded 'closed'
MRs despite the main scoring query including them with a decay
multiplier.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Planning document for the ongoing test extraction and code organization
effort. Covers module-by-module analysis, proposed file splits, and
phased execution plan.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New module: core::file_history with resolve_rename_chain() that traces
a file path through its rename history in mr_file_changes using
bidirectional BFS (forward: old_path->new_path, backward: new_path->old_path).
Key design decisions:
- Depth-bounded BFS: each queue entry carries its distance from the
origin, so max_hops correctly limits by graph distance (not by total
nodes discovered). This matters for branching rename graphs where a
file was renamed differently in parallel MRs.
- Cycle-safe: visited set prevents infinite loops from circular renames.
- Project-scoped: queries are always scoped to a single project_id.
- Deterministic: output is sorted for stable results.
Tests cover: linear chains (forward/backward), cycles, max_hops=0,
depth-bounded linear chains, branching renames, diamond patterns,
and cross-project isolation (9 tests total).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Factor out parse_since_from(input, reference_ms) so callers can compute
relative durations against a fixed reference timestamp instead of always
using now(). The existing parse_since() now delegates to it with now_ms().
Enables testable and reproducible time-relative queries for features like
timeline --as-of and who --as-of.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move inline #[cfg(test)] mod tests { ... } blocks from 22 source files
into dedicated _tests.rs companion files, wired via:
#[cfg(test)]
#[path = "module_tests.rs"]
mod tests;
This keeps implementation-focused source files leaner and more scannable
while preserving full access to private items through `use super::*;`.
Modules extracted:
core: db, note_parser, payloads, project, references, sync_run,
timeline_collect, timeline_expand, timeline_seed
cli: list (55 tests), who (75 tests)
documents: extractor (43 tests), regenerator
embedding: change_detector, chunking
gitlab: graphql (wiremock async tests), transformers/issue
ingestion: dirty_tracker, discussions, issues, mr_diffs
Also adds conflicts_with("explain_score") to the --detail flag in the
who command to prevent mutually exclusive flags from being combined.
All 629 unit tests pass. No behavior changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enriched all per-note search beads (NOTE-0A through NOTE-2I) with:
- Corrected migration numbers (022, 024, 025)
- Verified file paths and line numbers from codebase
- Complete function signatures for referenced code
- Detailed approach sections with SQL and Rust patterns
- DocumentData struct field mappings
- TDD anchors with specific test names
- Edge cases from codebase analysis
- Dependency context explaining what each blocker provides
Add quick_start section with glab equivalents, lore-exclusive features,
and read/write split guidance. Add example_output to issues, mrs, search,
and who commands. Update strip_schemas to also strip example_output in
brief mode. Update beads tracking state.
Closes: bd-91j1
Make run_search async, replace hardcoded lexical mode with SearchMode::parse(),
wire search_hybrid() with OllamaClient for semantic/hybrid modes, graceful
degradation when Ollama unavailable.
Closes: bd-1ksf
Implement drift detection using cosine similarity between issue description
embedding and chronological note embeddings. Sliding window (size 3) identifies
topic drift points. Includes human and robot output formatters.
New files: drift.rs, similarity.rs
Closes: bd-1cjx
Add references_full, user_notes_count, merge_requests_count computed
fields to show issue. Add closed_at and confidential columns via
migration 023.
Closes: bd-2g50
TUI PRD v2 (frankentui): Rounds 10-11 feedback refining the hybrid
Ratatui terminal UI approach — component architecture, keybinding
model, and incremental search integration.
Time-decay expert scoring: Round 6 feedback on the weighted scoring
model for the `who` command's expert mode, covering decay curves,
activity normalization, and bot filtering thresholds.
Plan-to-beads v2: Draft specification for the next iteration of the
plan-to-beads skill that converts markdown plans into dependency-
aware beads with full agent-executable context.
Per-note search PRD: Comprehensive product requirements for evolving
the search system from document-level to note-level granularity.
Includes 6 rounds of iterative feedback refining scope, ranking
strategy, migration path, and robot mode integration.
User journeys: Detailed walkthrough of 8 primary user workflows
covering issue triage, MR review lookup, code archaeology, expert
discovery, sync pipeline operation, and agent integration patterns.
Excalidraw source files and PNG exports for 5 architectural diagrams:
01-human-flow-map: User journey through lore CLI commands
02-agent-flow-map: AI agent interaction patterns with robot mode
03-command-coverage: Matrix of CLI commands vs data entities
04-gap-priority-matrix: Feature gap analysis with priority scoring
05-data-flow-architecture: End-to-end data pipeline from GitLab
through ingestion, storage, indexing, and query layers
User-supplied project names containing `%` or `_` were passed directly
into LIKE patterns, causing unintended wildcard matching. For example,
`my_project` would match `my-project` because `_` is a single-char
wildcard in SQL LIKE.
Added escape_like() helper that escapes `\`, `%`, and `_` with
backslash, and added ESCAPE '\' clauses to both the suffix-match and
substring-match queries in resolve_project().
Includes two regression tests:
- test_underscore_not_wildcard: `_` in input must not match `-`
- test_percent_not_wildcard: `%` in input must not match arbitrary strings
Query optimizer fixes for the `who` and `stats` commands based on
a systematic performance audit of the SQLite query plans.
who command (expert/reviews/detail modes):
- Add INDEXED BY idx_notes_diffnote_path_created hints to all DiffNote
queries. SQLite's planner was selecting idx_notes_system (38% of rows)
over the far more selective partial index (9.3% of rows). Measured
50-133x speedup on expert queries, 26x on reviews queries.
- Reorder JOIN clauses in detail mode's MR-author sub-select to match
the index scan direction (notes -> discussions -> merge_requests).
stats command:
- Replace 12+ sequential COUNT(*) queries with conditional aggregates
(COALESCE + SUM + CASE). Documents, dirty_sources, pending_discussion_
fetches, and pending_dependent_fetches tables each scanned once instead
of 2-3 times. Measured 1.7x speedup (109ms -> 65ms warm cache).
- Switch FTS document count from COUNT(*) on the virtual table to
COUNT(*) on documents_fts_docsize shadow table (B-tree scan vs FTS5
virtual table overhead). Measured 19x speedup for that single query.
Database: 61652 docs, 282K notes, 211K discussions, 1.5GB.
Updates README.md to explain the new defaultProject behavior:
- Config example now shows the defaultProject field
- New row in the configuration reference table describing the field,
its type (optional string), default (none), and behavior (fallback
when -p omitted, must match a configured path, CLI always overrides)
- Project Resolution section updated to explain the cascading logic:
CLI flag > config default > all projects
- Init section notes the interactive prompt for multi-project setups
and the --default-project flag for non-interactive/robot mode
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Integrates the defaultProject config field across the entire CLI
surface so that omitting `-p` now falls back to the configured default.
Init command:
- New `--default-project` flag on `lore init` (and robot-mode variant)
- InitInputs.default_project: Option<String> passed through to run_init
- Validation in run_init ensures the default matches a configured path
- Interactive mode: when multiple projects are configured, prompts
whether to set a default and which project to use
- Robot mode: InitOutputJson now includes default_project (omitted when
null) for downstream automation
- Autocorrect dictionary updated with `--default-project`
Command handlers applying effective_project():
- handle_issues: list filters use config default when -p omitted
- handle_mrs: same cascading resolution for MR listing
- handle_ingest: dry-run and full sync respect the default
- handle_timeline: TimelineParams.project resolved via effective_project
- handle_search: SearchCliFilters.project resolved via effective_project
- handle_generate_docs: project filter cascades
- handle_who: falls back to config.default_project when -p omitted
- handle_count: both count subcommands respect the default
- handle_discussions: discussion count filters respect the default
Robot-docs:
- init command schema updated with --default-project flag and
response_schema showing default_project as string?
- New config_notes section documents the defaultProject field with
type, description, and example
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduces a new optional `defaultProject` field on Config (and
MinimalConfig for init output) that acts as a fallback when the
`-p`/`--project` CLI flag is omitted.
Domain-layer changes:
- Config.default_project: Option<String> with camelCase serde rename
- Config::load validates that defaultProject matches a configured
project path (exact or case-insensitive suffix match), returning
ConfigInvalid on mismatch
- Config::effective_project(cli_flag) -> Option<&str>: cascading
resolver that prefers the CLI flag, then the config default, then None
- MinimalConfig.default_project with skip_serializing_if for clean
JSON output when unset
Tests added:
- effective_project: CLI overrides default, falls back to default,
returns None when both absent
- Config::load: accepts valid defaultProject, rejects nonexistent,
accepts suffix match
- MinimalConfig: omits null defaultProject, includes when set
- Helper write_config_with_default_project for parameterized tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add StatusEnrichmentStarted/PageFetched/Writing progress events so
sync no longer has a 45-60s silent gap during GraphQL status fetch
- Thread per-page callback into fetch_issue_statuses_with_progress
- Hide status_category from all human and robot output (keep in DB)
- Add meta.available_statuses to issues list JSON response for agent
self-discovery of valid --status filter values
- Update robot-docs with status filtering documentation
(Supersedes empty commit f3788eb — jj auto-snapshot race.)
Three related refinements to how work item status is presented:
1. available_statuses in meta (list.rs, main.rs):
Robot-mode issue list responses now include meta.available_statuses —
a sorted array of all distinct status_name values in the database.
Agents can use this to validate --status filter values or display
valid options without a separate query.
2. Hide status_category from JSON (list.rs, show.rs):
status_category is a GitLab internal classification that duplicates
the state field. Switched to skip_serializing so it never appears
in JSON output while remaining available internally.
3. Simplify human-readable status display (show.rs):
Removed the "(category)" parenthetical from the Status line.
4. robot-docs schema updates (main.rs):
Documented --status filter semantics and meta.available_statuses.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Establishes Jujutsu (jj) as the preferred VCS tool for this colocated
repo, matching the global Claude Code rules. Agents should use jj
equivalents for all git operations and only fall back to raw git for
hooks, LFS, submodules, or gh CLI interop.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>