Commit Graph

312 Commits

Author SHA1 Message Date
teernisse
af167e2086 test(asupersync): add cancellation, parity, and E2E acceptance tests
- Add 7 cancellation integration tests (ShutdownSignal, transaction rollback)
- Add 7 HTTP behavior parity tests (redirect, proxy, keep-alive, DNS, TLS)
- Add 9 E2E runtime acceptance tests (lifecycle, cancel+resume, tracing, HTTP pipeline)
- Total: 1190 tests, all passing

Phases 4-5 of asupersync migration.
2026-03-06 16:09:41 -05:00
teernisse
e8d6c5b15f feat(runtime): replace tokio+reqwest with asupersync async runtime
- Add HTTP adapter layer (src/http.rs) wrapping asupersync h1 client
- Migrate gitlab client, graphql, and ollama to HTTP adapter
- Swap entrypoint from #[tokio::main] to RuntimeBuilder::new().block_on()
- Rewrite signal handler for asupersync (RuntimeHandle::spawn + ctrl_c())
- Migrate rate limiter sleeps to asupersync::time::sleep(wall_now(), d)
- Add asupersync-native HTTP integration tests
- Convert timeline_seed_tests to RuntimeBuilder pattern

Phases 1-3 of asupersync migration (atomic: code won't compile without all pieces).
2026-03-06 15:57:20 -05:00
teernisse
bf977eca1a refactor(structure): reorganize codebase into domain-focused modules 2026-03-06 15:24:09 -05:00
teernisse
4d41d74ea7 refactor(deps): replace tokio Mutex/join!, add NetworkErrorKind enum, remove reqwest from error types 2026-03-06 15:22:42 -05:00
teernisse
3a4fc96558 refactor(shutdown): extract 4 identical Ctrl+C handlers into core/shutdown.rs 2026-03-06 15:22:37 -05:00
teernisse
ac5602e565 docs(plans): expand asupersync migration with decision gates, rollback, and invariants
Major additions to the migration plan based on review feedback:

Alternative analysis:
- Add "Why not tokio CancellationToken + JoinSet?" section explaining
  why obligation tracking and single-migration cost favor asupersync
  over incremental tokio fixes.

Error handling depth:
- Add NetworkErrorKind enum design for preserving error categories
  (timeout, DNS, TLS, connection refused) without coupling LoreError
  to any HTTP client.
- Add response body size guard (64 MiB) to prevent unbounded memory
  growth from misconfigured endpoints.

Adapter layer refinements:
- Expand append_query_params with URL fragment handling, edge case
  docs, and doc comments.
- Add contention constraint note for std::sync::Mutex rate limiter.

Cancellation invariants (INV-1 through INV-4):
- Atomic batch writes, no .await between tx open/commit,
  ShutdownSignal + region cancellation complementarity.
- Concrete test plan for each invariant.

Semantic ordering concerns:
- Document 4 behavioral differences when replacing join_all with
  region-spawned tasks (ordering, error aggregation, backpressure,
  late result loss on cancellation).

HTTP behavior parity:
- Replace informational table with concrete acceptance criteria and
  pass/fail tests for redirects, proxy, keep-alive, DNS, TLS, and
  Content-Length.

Phasing refinements:
- Add Cx threading sub-steps (orchestration path first, then
  command/embedding layer) for blast radius reduction.
- Add decision gate between Phase 0d and Phase 1 requiring compile +
  behavioral smoke tests before committing to runtime swap.

Rollback strategy:
- Per-phase rollback guidance with concrete escape hatch triggers
  (nightly breakage > 7d, TLS incompatibility, API instability,
  wiremock issues).

Testing depth:
- Adapter-layer test gap analysis with 5 specific asupersync-native
  integration tests.
- Cancellation integration test specifications.
- Coverage gap documentation for wiremock-on-tokio tests.

Risk register additions:
- Unbounded response body buffering, manual URL/header handling
  correctness.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:36:56 -05:00
teernisse
d3f8020cf8 perf(me): optimize mentions query with materialized CTEs scoped to candidates
The `query_mentioned_in` SQL previously joined notes directly against
the full issues/merge_requests tables, with per-row subqueries for
author/assignee/reviewer exclusion. On large databases this produced
pathological query plans where SQLite scanned the entire notes table
before filtering to relevant entities.

Refactor into a dedicated `build_mentioned_in_sql()` builder that:

1. Pre-filters candidate issues and MRs into MATERIALIZED CTEs
   (state open OR recently closed, not authored by user, not
   assigned/reviewing). This narrows the working set before any
   notes join.

2. Computes note timestamps (my_ts, others_ts, any_ts) as separate
   MATERIALIZED CTEs scoped to candidate entities only, rather than
   scanning all notes.

3. Joins mention-bearing notes against the pre-filtered candidates,
   avoiding the full-table scans.

Also adds a test verifying that authored issues are excluded from the
mentions results, and a unit test asserting all four CTEs are
materialized.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:36:37 -05:00
teernisse
9107a78b57 perf(ingestion): replace per-row INSERT loops with chunked batch INSERTs
The issue and MR ingestion paths previously inserted labels, assignees,
and reviewers one row at a time inside a transaction. For entities with
many labels or assignees, this issued N separate SQLite statements where
a single multi-row INSERT suffices.

Replace the per-row loops with batch INSERT functions that build a
single `INSERT OR IGNORE ... VALUES (?1,?2),(?1,?3),...` statement per
chunk. Chunks are capped at 400 rows (BATCH_LINK_ROWS_MAX) to stay
comfortably below SQLite's default 999 bind-parameter limit.

Affected paths:
- issues.rs: link_issue_labels_batch_tx, insert_issue_assignees_batch_tx
- merge_requests.rs: insert_mr_labels_batch_tx,
  insert_mr_assignees_batch_tx, insert_mr_reviewers_batch_tx

New tests verify deduplication (OR IGNORE), multi-chunk correctness,
and equivalence with the old per-row approach. A perf benchmark
(bench_issue_assignee_insert_individual_vs_batch) demonstrates the
speedup across representative assignee set sizes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:36:26 -05:00
teernisse
5fb27b1fbb chore: remove obsolete config files
Remove configuration files that are no longer used:

- .opencode/rules: OpenCode rules file, superseded by project CLAUDE.md
  and ~/.claude/ rules directory structure
- .roam/fitness.yaml: Roam fitness tracking config, unrelated to this
  project

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-06 11:16:17 -05:00
teernisse
2ab57d8d14 chore(plans): remove ephemeral review feedback files
Remove iterative feedback files that were used during plan development.
These files captured review rounds but are no longer needed now that the
plans have been finalized:

- plans/lore-service.feedback-{1,2,3,4}.md
- plans/time-decay-expert-scoring.feedback-{1,2,3,4}.md
- plans/tui-prd-v2-frankentui.feedback-{1,2,3,4,5,6,7,8,9}.md

The canonical plan documents remain; only the review iteration artifacts
are removed to reduce clutter.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-06 11:16:12 -05:00
teernisse
77445f6903 docs(plans): add asupersync migration plan
Draft plan for replacing Tokio + Reqwest with Asupersync, a cancel-correct
async runtime with structured concurrency guarantees.

Motivation:
- Current Ctrl+C during join_all silently drops in-flight HTTP requests
- ShutdownSignal is a hand-rolled AtomicBool with no structured cancellation
- No deterministic testing for concurrent ingestion patterns
- Tokio provides no structured concurrency guarantees

Plan structure:
- Complete inventory of tokio/reqwest usage in production and test code
- Phase 0: Preparation (reduce tokio surface before swap)
  - Extract signal handler to single function
  - Replace tokio::sync::Mutex with std::sync::Mutex where appropriate
  - Create HTTP adapter trait for pluggable backends
- Phase 1-5: Progressive migration with detailed implementation steps

Trade-offs accepted:
- Nightly Rust required (asupersync dependency)
- Pre-1.0 runtime dependency (mitigated by adapter layer + version pinning)
- Deeper function signature changes for Cx threading

This is a reference document for future implementation, not an immediate
change to the runtime.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-06 11:15:58 -05:00
teernisse
87249ef3d9 feat(agents): add CEO and Founding Engineer agent configurations
Establish multi-agent infrastructure with two initial agent roles:

CEO Agent (agents/ceo/):
- AGENTS.md: Root configuration defining home directory conventions,
  memory system integration (para-memory-files skill), safety rules
- HEARTBEAT.md: Execution checklist covering identity verification,
  local planning review, approval follow-ups, assignment processing,
  delegation patterns, fact extraction, and clean exit protocol
- SOUL.md: Persona definition with strategic posture (P&L ownership,
  action bias, focus protection) and voice/tone guidelines (direct,
  plain language, async-friendly formatting)
- TOOLS.md: Placeholder for tool acquisition notes
- memory/2026-03-05.md: First daily notes with timeline entries and
  observations about environment setup

Founding Engineer Agent (agents/founding-engineer/):
- AGENTS.md: IC-focused configuration for primary code contributor,
  references project CLAUDE.md for toolchain conventions, includes
  quality gate reminders (cargo check/clippy/fmt)

This structure supports the Paperclip-style agent coordination system
where agents have dedicated home directories, memory systems, and
role-specific execution checklists.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-06 11:15:47 -05:00
teernisse
f6909d822e docs: add documentation for me, related, and init --refresh commands
Update CLAUDE.md and README.md with documentation for recently added
features:

CLAUDE.md:
- Add robot mode examples for `lore --robot related`
- Add example for `lore --robot init --refresh`

README.md:
- Add full documentation section for `lore me` command including all
  flags (--issues, --mrs, --mentions, --activity, --since, --project,
  --all, --user, --reset-cursor) and section descriptions
- Add documentation section for `lore related` command with entity mode
  and query mode examples
- Expand `lore init` section with --refresh flag documentation explaining
  project registration workflow
- Add quick examples in the features section
- Update version number in example output (0.9.2)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-06 11:15:36 -05:00
teernisse
1dfcfd3f83 feat(autocorrect): add fuzzy subcommand matching and flag-as-subcommand detection
Extend the CLI autocorrection pipeline with two new correction rules that
help agents recover from common typos and misunderstandings:

1. SubcommandFuzzy (threshold 0.85): Fuzzy-matches typo'd subcommands
   against the canonical list. Examples:
   - "issuess" → "issues"
   - "timline" → "timeline"
   - "serach" → "search"
   
   Guards prevent false positives:
   - Words that look like misplaced global flags are skipped
   - Valid command prefixes are left to clap's infer_subcommands

2. FlagAsSubcommand: Detects when agents type subcommands as flags.
   Some agents (especially Codex) assume `--robot-docs` is a flag rather
   than a subcommand. This rule converts:
   - "--robot-docs" → "robot-docs"
   - "--generate-docs" → "generate-docs"

Also improves error messages in main.rs:
- MissingRequiredArgument: Contextual example based on detected subcommand
- MissingSubcommand: Lists common commands
- TooFewValues/TooManyValues: Command-specific help hints

Added CANONICAL_SUBCOMMANDS constant enumerating all valid subcommands
(including hidden ones) for fuzzy matching. This ensures agents that know
about hidden commands still get typo correction.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-06 11:15:28 -05:00
teernisse
ffbd1e2dce feat(me): add mentions section for @-mentions in dashboard
Add a new --mentions flag to the `lore me` command that surfaces items
where the user is @-mentioned but NOT already assigned, authoring, or
reviewing. This fills an important gap in the personal work dashboard:
cross-team requests and callouts that don't show up in the standard
issue/MR sections.

Implementation details:
- query_mentioned_in() scans notes for @username patterns, then filters
  out entities where the user is already an assignee, author, or reviewer
- MentionedInItem type captures entity_type (issue/mr), iid, title, state,
  project path, attention state, and updated timestamp
- Attention state computation marks items as needs_attention when there's
  recent activity from others
- Recency cutoff (7 days) prevents surfacing stale mentions
- Both human and robot renderers include the new section

The robot mode schema adds mentioned_in array with me_mentions field
preset for token-efficient output.

Test coverage:
- mentioned_in_finds_mention_on_unassigned_issue: basic case
- mentioned_in_excludes_assigned_issue: no duplicate surfacing
- mentioned_in_excludes_author_on_mr: author already sees in authored MRs
- mentioned_in_excludes_reviewer_on_mr: reviewer already sees in reviewing
- mentioned_in_uses_recency_cutoff: old mentions filtered
- mentioned_in_respects_project_filter: scoping works

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-06 11:15:15 -05:00
teernisse
571c304031 feat(init): add --refresh flag for project re-registration
When new projects are added to the config file, `lore sync` doesn't pick
them up because project discovery only happens during `lore init`. 
Previously, users had to use `--force` to overwrite their entire config.

The new `--refresh` flag reads the existing config and updates the
database to match, without modifying the config file itself.

Features:
- Validates GitLab authentication before processing
- Registers new projects from config into the database
- Detects orphan projects (in DB but removed from config)
- Interactive mode: prompts to delete orphans (default: No)
- Robot mode: returns JSON with orphan info, no prompts

Usage:
  lore init --refresh              # Interactive
  lore --robot init --refresh      # JSON output

Improved UX: When running `lore init` with an existing config and no
flags, the error message now suggests using `--refresh` to register
new projects or `--force` to overwrite the config file.

Implementation:
- Added RefreshOptions and RefreshResult types to init module
- Added run_init_refresh() for core refresh logic
- Added delete_orphan_projects() helper for orphan cleanup
- Added handle_init_refresh() in main.rs for CLI handling
- Added JSON output types for robot mode
- Registered --refresh in autocorrect.rs command flags registry
- --refresh conflicts with --force (mutually exclusive)
2026-03-02 15:23:41 -05:00
teernisse
e4ac7020b3 chore: remove ephemeral HTML review files
These HTML files were generated for one-time analysis/review purposes
and should not be tracked in the repository.

Files removed:
- api-review.html
- gitlore-sync-explorer.html  
- phase-a-review.html
2026-03-02 15:23:20 -05:00
teernisse
c7a7898675 release: v0.9.2 2026-03-02 14:17:31 -05:00
teernisse
5fd1ce6905 perf(ingestion): implement prefetch pattern for issue discussions
Issue discussion sync was ~10x slower than MR discussion sync because it
used a fully sequential pattern: fetch one issue's discussions, write to
DB, repeat. MR sync already used a prefetch pattern with concurrent HTTP
requests followed by sequential DB writes.

This commit brings issue discussion sync to parity with MRs:

Architecture (prefetch pattern):
  1. HTTP phase: Concurrent fetches via `join_all()` with batch size
     controlled by `dependent_concurrency` config (default 8)
  2. Transform phase: Normalize discussions and notes during prefetch
  3. DB phase: Sequential writes with proper transaction boundaries

Changes:
  - gitlab/client.rs: Add `fetch_all_issue_discussions()` to mirror
    the existing MR pattern for API consistency
  - discussions.rs: Replace `ingest_issue_discussions()` with:
    * `prefetch_issue_discussions()` - async HTTP fetch + transform
    * `write_prefetched_issue_discussions()` - sync DB writes
    * New structs: `PrefetchedIssueDiscussions`, `PrefetchedDiscussion`
  - orchestrator.rs: Update `sync_discussions_sequential()` to use
    concurrent prefetch for each batch instead of sequential calls
  - surgical.rs: Update single-issue surgical sync to use new functions
  - mod.rs: Update public exports

Expected improvement: 5-10x speedup on issue discussion sync (from ~50s
to ~5-10s for large projects) due to concurrent HTTP round-trips.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-02 14:14:03 -05:00
teernisse
b67bb8754c fix(who): prevent integer overflow in limit calculations
When `--limit` is omitted, the default value is `usize::MAX` to mean
"unlimited". The previous code used `(limit + 1) as i64` to fetch one
extra row for "has more" detection. This caused integer overflow:

  usize::MAX + 1 = 0  (wraps around)

The resulting `LIMIT 0` clause returned zero rows, making the `who`
subcommands appear to find nothing even when data existed.

Fix: Use `saturating_add(1)` to cap at `usize::MAX` instead of wrapping,
then `.min(i64::MAX as usize)` to ensure the value fits in SQLite's
signed 64-bit LIMIT parameter.

Includes regression tests that verify `usize::MAX` limit returns results.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-02 14:13:51 -05:00
teernisse
3f38b3fda7 docs: add comprehensive command surface analysis
Deep analysis of the full `lore` CLI command surface (34 commands across
6 categories) covering command inventory, data flow, overlap analysis,
and optimization proposals.

Document structure:
- Main consolidated doc: docs/command-surface-analysis.md (1251 lines)
- Split sections in docs/command-surface-analysis/ for navigation:
  00-overview.md      - Summary, inventory, priorities
  01-entity-commands.md   - issues, mrs, notes, search, count
  02-intelligence-commands.md - who, timeline, me, file-history, trace, related, drift
  03-pipeline-and-infra.md    - sync, ingest, generate-docs, embed, diagnostics
  04-data-flow.md     - Shared data source map, command network graph
  05-overlap-analysis.md  - Quantified overlap percentages for every command pair
  06-agent-workflows.md   - Common agent flows, round-trip costs, token profiles
  07-consolidation-proposals.md  - 5 proposals to reduce 34 commands to 29
  08-robot-optimization-proposals.md - 6 proposals for --include, --batch, --depth
  09-appendices.md    - Robot output envelope, field presets, exit codes

Key findings:
- High overlap pairs: who-workload/me (~85%), health/doctor (~90%)
- 5 consolidation proposals to reduce command count by 15%
- 6 robot-mode optimization proposals targeting agent round-trip reduction
- Full DB table mapping and data flow documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-28 00:08:31 -05:00
teernisse
439c20e713 release: v0.9.1 2026-02-26 11:39:05 -05:00
teernisse
fd0a40b181 chore: update beads and GitLab TODOs integration plan
Update beads issue tracking state and expand the GitLab TODOs
notifications integration design document with additional
implementation details.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-26 11:07:04 -05:00
teernisse
b2811b5e45 fix(fts): remove NEAR from infix operator list
NEAR is an FTS5 function (NEAR(term1 term2, N)), not an infix operator like
AND/OR/NOT. Passing it through unquoted in Safe mode was incorrect - it would
be treated as a literal term rather than a function call.

Users who need NEAR proximity search should use FtsQueryMode::Raw which
passes the query through verbatim to FTS5.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-26 11:06:59 -05:00
teernisse
2d2e470621 refactor(orchestrator): consolidate stale lock reclamation and fix edge cases
Several improvements to the ingestion orchestrator:

1. Stale lock reclamation consolidation:
   Previously, reclaim_stale_locks() was called redundantly in multiple
   drain functions (drain_resource_events, drain_closes_issues, etc.).
   Now it's called once at sync entry points (ingest_project_issues,
   ingest_project_mrs) to reduce overhead and DB contention.

2. Fix status_enrichment_mode error values:
   - "fetched" -> "error" when project path is missing
   - "fetched" -> "fetch_error" when GraphQL fetch fails
   These values are used in robot mode JSON output and should accurately
   reflect the error condition.

3. Add batch_size zero guard:
   Added .max(1) to batch_size calculation to prevent panic in .chunks()
   when config.sync.dependent_concurrency is 0. This makes the code
   defensive against misconfiguration.

These changes improve correctness and reduce unnecessary DB operations
during sync, particularly beneficial for large projects with many entities.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-26 11:06:44 -05:00
teernisse
23efb15599 feat(truncation): add pre-truncation for oversized descriptions
Add pre_truncate_description() to prevent unbounded memory allocation when
processing pathologically large descriptions (e.g., 500MB base64 blobs in
issue descriptions).

Previously, the document extraction pipeline would:
1. Allocate memory for the entire description
2. Append to content buffer
3. Only truncate at the end via truncate_hard_cap()

For a 500MB description, this would allocate 500MB+ before truncation.

New approach:
1. Check description size BEFORE appending
2. If over limit, truncate at UTF-8 boundary immediately
3. Add human-readable marker: "[... description truncated from 500.0MB to 2.0MB ...]"
4. Log warning with original size for observability

Also adds format_bytes() helper for human-readable byte sizes (B, KB, MB).

This is applied to both issue and MR document extraction in extractor.rs,
protecting the embedding pipeline from OOM on malformed GitLab data.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-26 11:06:32 -05:00
teernisse
a45c37c7e4 feat(timeline): add entity-direct seeding and round-robin evidence selection
Enhance the timeline command with two major improvements:

1. Entity-direct seeding syntax (bypass search):
   lore timeline issue:42    # Timeline for specific issue
   lore timeline i:42        # Short form
   lore timeline mr:99       # Timeline for specific MR
   lore timeline m:99        # Short form

   This directly resolves the entity and gathers ALL its discussions without
   requiring search/embedding. Useful when you know exactly which entity you want.

2. Round-robin evidence note selection:
   Previously, evidence notes were taken in FTS rank order, which could result
   in all notes coming from a single high-traffic discussion. Now we:
   - Fetch 5x the requested limit (or minimum 50)
   - Group notes by discussion_id
   - Select round-robin across discussions
   - This ensures diverse evidence from multiple conversations

API changes:
- Renamed total_events_before_limit -> total_filtered_events (clearer semantics)
- Added resolve_entity_by_iid() in timeline.rs for IID-based entity resolution
- Added seed_timeline_direct() in timeline_seed.rs for search-free seeding
- Added round_robin_select_by_discussion() helper function

The entity-direct mode uses search_mode: "direct" to distinguish from
"hybrid" or "lexical" search modes in the response metadata.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-26 11:06:23 -05:00
teernisse
8657e10822 feat(related): add semantic similarity discovery command
Implement `lore related` command for discovering semantically similar entities
using vector embeddings. Supports two modes:

Entity mode:
  lore related issues 42     # Find entities similar to issue #42
  lore related mrs 99        # Find entities similar to MR !99

Query mode:
  lore related "auth bug"    # Find entities matching free text query

Key features:
- Uses existing embedding infrastructure (nomic-embed-text via Ollama)
- Computes shared labels between source and results
- Shows similarity scores as percentage (0-100%)
- Warns when all results have low similarity (<30%)
- Warns for short queries (<=2 words) that may produce noisy results
- Filters out discussion/note documents, returning only issues and MRs
- Handles orphaned documents gracefully (skips if entity deleted)
- Robot mode JSON output with {ok, data, meta} envelope

Implementation details:
- distance_to_similarity() converts L2 distance to 0-1 score: 1/(1+distance)
- Uses saturating_add/saturating_mul for overflow safety on limit parameter
- Proper error handling for missing embeddings ("run lore embed first")
- Project scoping via -p flag with fuzzy matching

CLI integration:
- Added to autocorrect.rs command registry
- Added Related variant to Commands enum in cli/mod.rs
- Wired into main.rs with handle_related()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-26 11:06:12 -05:00
teernisse
7fdeafa330 feat(db): add migration 028 for discussions.merge_request_id FK constraint
Add foreign key constraint on discussions.merge_request_id to prevent orphaned
discussions when MRs are deleted. SQLite doesn't support ALTER TABLE ADD CONSTRAINT,
so this migration recreates the table with:

1. New table with FK: REFERENCES merge_requests(id) ON DELETE CASCADE
2. Data copy with FK validation (only copies rows with valid MR references)
3. Table swap (DROP old, RENAME new)
4. Full index recreation (all 10 indexes from migrations 002-022)

The migration also includes a CHECK constraint ensuring mutual exclusivity:
- Issue discussions have issue_id NOT NULL and merge_request_id NULL
- MR discussions have merge_request_id NOT NULL and issue_id NULL

Also fixes run_migrations() to properly propagate query errors instead of
silently returning unwrap_or defaults, improving error diagnostics.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-26 11:06:01 -05:00
teernisse
0fe3737035 docs(plan): add GitLab TODOs integration design document
Captures design decisions and acceptance criteria for adding GitLab
TODO support to lore. This plan was developed through user interview
to ensure the feature aligns with actual workflows.

Key design decisions:
- Read-only scope (no mark-as-done operations)
- Three integration points: --todos flag, activity enrichment, lore todos
- Account-wide: --project does NOT filter todos (unlike issues/MRs)
- Separate signal: todos don't affect attention state calculation
- Snapshot sync: missing todos = marked done elsewhere = delete locally

The plan covers:
- Database schema (todos table + indexes)
- GitLab API client extensions
- Sync pipeline integration
- Action type handling and grouping
- CLI commands and robot mode schemas
- Non-synced project handling with [external] indicator

Implementation is organized into 5 rollout slices:
A: Schema + Client, B: Sync, C: lore todos, D: lore me, E: Polish

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 10:02:55 -05:00
teernisse
87bdbda468 feat(status): add per-entity sync counts from migration 027
Enhances sync status reporting to include granular per-entity counts
that were added in database migration 027. This provides better
visibility into what each sync run actually processed.

New fields in SyncRunInfo and robot mode JSON:
- issues_fetched / issues_ingested: issue sync counts
- mrs_fetched / mrs_ingested: merge request sync counts
- skipped_stale: entities skipped due to staleness
- docs_regenerated / docs_embedded: document pipeline counts
- warnings_count: non-fatal issues during sync

Robot mode optimization:
- Uses skip_serializing_if = "is_zero" to omit zero-value fields
- Reduces JSON payload size for typical sync runs
- Maintains backwards compatibility (fields are additive)

SQL query now reads all 8 new columns from sync_runs table,
with defensive unwrap_or(0) for NULL handling.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 10:02:45 -05:00
teernisse
ed987c8f71 docs: update robot-docs manifest and agent instructions for since-last-check
Updates the `lore robot-docs` manifest with comprehensive documentation
for the new since-last-check inbox feature, enabling AI agents to
discover and use the functionality programmatically.

robot-docs manifest additions:
- since_last_check response schema with cursor_iso, groups, events
- --reset-cursor flag documentation
- Design notes: cursor persistence location, --project filter behavior
- Example commands in personal_dashboard section

Agent instruction updates (AGENTS.md, CLAUDE.md):
- Added --mrs, --project, --user flags to command examples
- Added --reset-cursor example
- Aligned both files for consistency

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 10:02:37 -05:00
teernisse
ce5621f3ed feat(me): add "since last check" cursor-based inbox to dashboard
Implements a cursor-based notification inbox that surfaces actionable
events from others since the user's last `lore me` invocation. This
addresses the core UX need: "what happened while I was away?"

Event Sources (three-way UNION query):
1. Others' comments on user's open issues/MRs
2. @mentions on ANY item (not restricted to owned items)
3. Assignment/review-request system notes mentioning user

Mention Detection:
- SQL LIKE pre-filter for performance, then regex validation
- Word-boundary-aware: rejects "alice" in "@alice-bot" or "alice@corp.com"
- Domain rejection: "@alice.com" not matched (prevents email false positives)
- Punctuation tolerance: "@alice," "@alice." "(@ alice)" all match

Cursor Watermark Pattern:
- Global watermark computed from ALL projects before --project filtering
- Ensures --project display filter doesn't permanently skip events
- Cursor advances only after successful render (no data loss on errors)
- First run establishes baseline (no inbox shown), subsequent runs show delta

Output:
- Human: color-coded event badges, grouped by entity, actor + timestamp
- Robot: standard envelope with since_last_check object containing
  cursor_iso, total_event_count, and groups array with nested events

CLI additions:
- --reset-cursor flag: clears cursor (next run shows no new events)
- Autocorrect: --reset-cursor added to known me command flags

Tests cover:
- Mention with trailing comma/period/parentheses (should match)
- Email-like text "@alice.com" (should NOT match)  
- Domain-like text "@alice.example" (should NOT match)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 10:02:31 -05:00
teernisse
eac640225f feat(core): add cursor persistence module for session-based timestamps
Introduces a lightweight file-based cursor system for persisting
per-user timestamps across CLI invocations. This enables "since last
check" semantics where `lore me` can track what the user has seen.

Key design decisions:
- Per-user cursor files: ~/.local/share/lore/me_cursor_<username>.json
- Atomic writes via temp-file + rename pattern (crash-safe)
- Graceful degradation: missing/corrupt files return None
- Username sanitization: non-safe chars replaced with underscore

The cursor module provides three operations:
- read_cursor(username) -> Option<i64>: read last-check timestamp
- write_cursor(username, timestamp_ms): atomically persist timestamp  
- reset_cursor(username): delete cursor file (no-op if missing)

Tests cover: missing file, roundtrip, per-user isolation, reset
isolation, JSON validity after overwrites, corrupt file handling.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 10:02:13 -05:00
teernisse
c5843bd823 release: v0.9.0 2026-02-23 10:49:44 -05:00
teernisse
f9e7913232 fix(error): replace misleading Database error suggestions
The Database(rusqlite::Error) catch-all variant was suggesting
'lore reset --yes' for ALL database errors, including transient
SQLITE_BUSY lock contention. This was wrong on two counts:
1. `lore reset` is not implemented (prints "not yet implemented")
2. Nuking the database is not the fix for a transient lock

Changes:
- Detect SQLITE_BUSY specifically via sqlite_error_code() and provide
  targeted advice: "Another process has the database locked" with
  common causes (cron sync, concurrent lore command)
- Map SQLITE_BUSY to ErrorCode::DatabaseLocked (exit code 9) instead
  of DatabaseError (exit code 10) — semantically correct
- Set BUSY actions to ["lore cron status"] (diagnostic) instead of
  the useless "lore sync --force" (--force overrides the app-level
  lock table, but SQLITE_BUSY fires before that table is even reached)
- Fix MigrationFailed suggestion: also referenced non-existent
  'lore reset', now says "try again" with lore migrate / lore doctor
- Non-BUSY database errors get a simpler suggestion pointing to
  lore doctor (no more phantom reset command)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:36:16 -05:00
teernisse
6e487532aa feat(me): improve dashboard rendering with dynamic layout and table-based activity
Overhaul the `lore me` human-mode renderer for better terminal adaptation
and visual clarity:

Layout:
- Add terminal_width() detection (COLUMNS env -> stderr ioctl -> 80 fallback)
- Replace hardcoded column widths with dynamic title_width() that adapts to
  terminal size, clamped to [20, 80]
- Section dividers now span the full terminal width

Activity feed:
- Replace manual println! formatting with Table-based rendering for proper
  column alignment across variable-width content
- Split event_badge() into activity_badge_label() + activity_badge_style()
  for table cell compatibility
- Add system_event_style() (#555555 dark gray) to visually suppress
  non-note events (label, assign, status, milestone, review changes)
- Own actions use dim styling; others' notes render at full color

MR display:
- Add humanize_merge_status() to convert GitLab API values like
  "not_approved" -> "needs approval", "ci_must_pass" -> "CI pending"

Table infrastructure (render.rs):
- Add Table::columns() for headerless tables
- Add Table::indent() for row-level indentation
- Add truncate_pad() for fixed-width cell formatting
- Table::render() now supports headerless mode (no separator line)

Other:
- Default activity lookback changed from 30d to 1d (more useful default)
- Robot-docs schema added for `me` command
- AGENTS.md and CLAUDE.md updated with `lore me` examples

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:36:01 -05:00
teernisse
7e9a23cc0f fix(me): include NULL statuses in open issues filter
Organizations without GitLab Premium/Ultimate don't have work item
statuses configured - all their issues have status_name = NULL.
Previously, the me command filtered to only 'In Progress' and
'In Review' statuses, showing zero issues for these organizations.

Now includes NULL status as a fallback for graceful degradation.
2026-02-21 09:20:25 -05:00
teernisse
71d07c28d8 fix(migrations): add schema_version inserts to migrations 022-027
Defense-in-depth: The migration framework already handles missing
inserts via INSERT OR REPLACE (db.rs:174), but adding explicit
inserts to .sql files ensures consistency and makes migrations
self-documenting.

Migrations affected:
- 022_notes_query_index
- 024_note_documents  
- 025_note_dirty_backfill
- 026_scoring_indexes
- 027_surgical_sync_runs
2026-02-21 09:20:18 -05:00
teernisse
f4de6feaa2 chore: gitignore .liquid-mail.toml and remove from tracking
The file contains a Honcho API key that should not be in version control.
Added to .gitignore and untracked; the file remains on disk for local use.
2026-02-20 14:54:10 -05:00
teernisse
ec0aaaf77c chore: update beads tracker state
Sync beads issue database to JSONL for version control tracking.
2026-02-20 14:31:57 -05:00
teernisse
9c1a9bfe5d feat(me): add lore me personal work dashboard command
Implement a personal work dashboard that shows everything relevant to the
configured GitLab user: open issues assigned to them, MRs they authored,
MRs they are reviewing, and a chronological activity feed.

Design decisions:
- Attention state computed from GitLab interaction data (comments, reviews)
  with no local state tracking -- purely derived from existing synced data
- Username resolution: --user flag > config.gitlab.username > actionable error
- Project scoping: --project (fuzzy) | --all | default_project | all
- Section filtering: --issues, --mrs, --activity (combinable, default = all)
- Activity feed controlled by --since (default 30d); work item sections
  always show all open items regardless of --since

Architecture (src/cli/commands/me/):
- types.rs: MeDashboard, MeSummary, AttentionState data types
- queries.rs: 4 SQL queries (open_issues, authored_mrs, reviewing_mrs,
  activity) using existing issue_assignees, mr_reviewers, notes tables
- render_human.rs: colored terminal output with attention state indicators
- render_robot.rs: {ok, data, meta} JSON envelope with field selection
- mod.rs: orchestration (resolve_username, resolve_project_scope, run_me)
- me_tests.rs: comprehensive unit tests covering all query paths

Config additions:
- New optional gitlab.username field in config.json
- Tests for config with/without username
- Existing test configs updated with username: None

CLI wiring:
- MeArgs struct with section filter, since, project, all, user, fields flags
- Autocorrect support for me command flags
- LoreRenderer::try_get() for safe renderer access in me module
- Robot mode field selection presets (me_items, me_activity)
- handle_me() in main.rs command dispatch

Also fixes duplicate assertions in surgical sync tests (removed 6
duplicate assert! lines that were copy-paste artifacts).

Spec: docs/lore-me-spec.md
2026-02-20 14:31:57 -05:00
teernisse
a5c2589c7d docs: migrate agent coordination from MCP Agent Mail to Liquid Mail
Replace all MCP Agent Mail references with Liquid Mail in AGENTS.md and
CLAUDE.md. The old system used file reservations and MCP-based messaging
with inbox/outbox/thread semantics. Liquid Mail provides a simpler
post-based shared log with topic-scoped messages, decision conflict
detection, and polling via the liquid-mail CLI.

Key changes:
- Remove entire MCP Agent Mail section (identity registration, file
  reservations, macros vs granular tools, common pitfalls)
- Update Beads integration workflow to reference Liquid Mail: replace
  reservation + announce patterns with post-based progress logging and
  decision-first workflows
- Update bv scope boundary note to reference Liquid Mail
- Append full Liquid Mail integration block to CLAUDE.md: conventions,
  typical flow, decision conflicts, posting format, topic rules, context
  refresh, live updates, mapping cheat-sheet, quick reference
- Add .liquid-mail.toml project configuration (Honcho backend)
2026-02-20 14:31:57 -05:00
teernisse
8fdb366b6d chore: close shipped epics and remove stale bead dependencies
Closed: bd-1nsl (surgical sync), bd-14q (file-history), bd-1ht (trace),
bd-1v8 (robot-docs update), bd-2fc (AGENTS.md update).
Removed stale blockers from bd-8con, bd-1n5q, bd-9lbr.
2026-02-18 16:52:24 -05:00
teernisse
53b093586b docs: update README and beads tracker state
Update README with documentation for surgical sync, token management,
code provenance tracing, file-level history, cron scheduling, and
configurable icon system. Add usage examples and environment variables.

Update beads issue tracker state.
2026-02-18 16:37:20 -05:00
teernisse
9ec1344945 feat(surgical-sync): add per-IID surgical sync pipeline with preflight validation
Add the ability to sync specific issues or merge requests by IID without
running a full incremental sync. This enables fast, targeted data refresh
for individual entities — useful for agent workflows, debugging, and
real-time investigation of specific issues or MRs.

Architecture:
- New CLI flags: --issue <IID> and --mr <IID> (repeatable, up to 100 total)
  scoped to a single project via -p/--project
- Preflight phase validates all IIDs exist on GitLab before any DB writes,
  with TOCTOU-aware soft verification at ingest time
- 6-stage pipeline: preflight -> fetch -> ingest -> dependents -> docs -> embed
- Each stage is cancellation-aware via ShutdownSignal
- Dedicated SyncRunRecorder extensions track surgical-specific counters
  (issues_fetched, mrs_ingested, docs_regenerated, etc.)

New modules:
- src/ingestion/surgical.rs: Core surgical fetch/ingest/dependent logic
  with preflight_fetch(), ingest_issue_by_iid(), ingest_mr_by_iid(),
  and fetch_dependents_for_{issue,mr}()
- src/cli/commands/sync_surgical.rs: Full CLI orchestrator with progress
  spinners, human/robot output, and cancellation handling
- src/embedding/pipeline.rs: embed_documents_by_ids() for scoped embedding
- src/documents/regenerator.rs: regenerate_dirty_documents_for_sources()
  for scoped document regeneration

Database changes:
- Migration 027: Extends sync_runs with mode, phase, surgical_iids_json,
  per-entity counters, and cancelled_at column
- New indexes: idx_sync_runs_mode_started, idx_sync_runs_status_phase_started

GitLab client:
- get_issue_by_iid() and get_mr_by_iid() single-entity fetch methods

Error handling:
- New SurgicalPreflightFailed error variant with entity_type, iid, project,
  and reason fields. Shares exit code 6 with GitLabNotFound.

Includes comprehensive test coverage:
- 645 lines of surgical ingestion tests (wiremock-based)
- 184 lines of scoped embedding tests
- 85 lines of scoped regeneration tests
- 113 lines of GitLab client single-entity tests
- 236 lines of sync_run surgical column/counter tests
- Unit tests for SyncOptions, error codes, and CLI validation
2026-02-18 16:28:21 -05:00
teernisse
ea6e45e43f refactor(who): make --limit optional (unlimited default) and fix clippy sort lints
Change the `who` command's --limit flag from default=20 to optional,
so omitting it returns all results. This matches the behavior users
expect when they want a complete expert/workload/active/overlap listing
without an arbitrary cap.

Also applies clippy-recommended sort improvements:
- who/reviews: sort_by(|a,b| b.count.cmp(&a.count)) -> sort_by_key with Reverse
- drift: same pattern for frequency sorting

Adds Theme::color_icon() helper to DRY the stage-icon coloring pattern
used in sync output (was inline closure, now shared method).
2026-02-18 16:27:59 -05:00
teernisse
30ed02c694 feat(token): add stored token support with resolve_token and token_source
Introduce a centralized token resolution system that supports both
environment variables and config-file-stored tokens with clear priority
(env var wins). This enables cron-based sync which runs in minimal
shell environments without env vars.

Core changes:
- GitLabConfig gains optional `token` field and `resolve_token()` method
  that checks env var first, then config file, returning trimmed values
- `token_source()` returns human-readable provenance ("environment variable"
  or "config file") for diagnostics
- `ensure_config_permissions()` enforces 0600 on config files containing
  tokens (Unix only, no-op on other platforms)

New CLI commands:
- `lore token set [--token VALUE]` — validates against GitLab API, stores
  in config, enforces file permissions. Supports flag, stdin pipe, or
  interactive entry.
- `lore token show [--unmask]` — displays masked token with source label

Consumers updated to use resolve_token():
- auth_test: removes manual env var lookup
- doctor: shows token source in health check output
- ingest: uses centralized resolution

Includes 10 unit tests for resolve/source logic and 2 for mask_token.
2026-02-18 16:27:48 -05:00
teernisse
a4df8e5444 docs: add CLAUDE.md project instructions and acceptance criteria
Add CLAUDE.md with comprehensive agent instructions covering:
- Version control (jj-first policy)
- Toolchain requirements (Rust/Cargo only, unsafe forbidden)
- Code editing discipline (no scripts, no file proliferation)
- Compiler check requirements (cargo check + clippy + fmt)
- Robot mode documentation with all commands, exit codes, and schemas
- Session completion workflow (landing the plane)
- Integration docs for beads, bv, cass, ast-grep, and warp_grep

Add acceptance-criteria.md documenting diagnostic improvements for
trace/file-history empty-result scenarios (AC-1 through AC-4).
2026-02-18 16:27:35 -05:00
teernisse
53ce20595b feat(cron): add lore cron command for automated sync scheduling
Add lore cron {install,uninstall,status} to manage a crontab entry that
runs lore sync on a configurable interval. Supports both human and robot
output modes.

Core implementation (src/core/cron.rs):
  - install_cron: appends a tagged crontab entry, detects existing entries
  - uninstall_cron: removes the tagged entry
  - cron_status: reads crontab + checks last-sync time from the database
  - Unix-only (#[cfg(unix)]) — compiles out on Windows

CLI wiring:
  - CronAction enum and CronArgs in cli/mod.rs with after_help examples
  - Robot JSON envelope with RobotMeta timing for all 3 sub-actions
  - Dispatch in main.rs

Also in this commit:
  - Add after_help example blocks to Status, Auth, Doctor, Init, Migrate,
    Health commands for better discoverability
  - Add LORE_ICONS env var documentation to CLI help text
  - Simplify notes format dispatch in main.rs (removed csv/jsonl paths)
  - Update commands/mod.rs re-exports for cron + notes cleanup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 13:29:20 -05:00