CLI audit scoring the current command surface across human ergonomics, robot/agent ergonomics, documentation quality, and flag design. Paired with a detailed implementation plan for restructuring commands into a more consistent, discoverable hierarchy.
967 lines
32 KiB
Markdown
967 lines
32 KiB
Markdown
# Command Restructure: Implementation Plan
|
|
|
|
**Reference:** `command-restructure/CLI_AUDIT.md`
|
|
**Scope:** 10 proposals, 3 implementation phases, estimated ~15 files touched
|
|
|
|
---
|
|
|
|
## Phase 1: Zero-Risk Quick Wins (1 commit)
|
|
|
|
These four changes are purely additive -- no behavior changes, no renames, no removed commands.
|
|
|
|
### P1: Help Grouping
|
|
|
|
**Goal:** Group the 29 visible commands into 5 semantic clusters in `--help` output.
|
|
|
|
**File:** `src/cli/mod.rs` (lines 117-399, the `Commands` enum)
|
|
|
|
**Changes:** Add `#[command(help_heading = "...")]` to each variant:
|
|
|
|
```rust
|
|
#[derive(Subcommand)]
|
|
#[allow(clippy::large_enum_variant)]
|
|
pub enum Commands {
|
|
// ── Query ──────────────────────────────────────────────
|
|
/// List or show issues
|
|
#[command(visible_alias = "issue", help_heading = "Query")]
|
|
Issues(IssuesArgs),
|
|
|
|
/// List or show merge requests
|
|
#[command(visible_alias = "mr", alias = "merge-requests", alias = "merge-request", help_heading = "Query")]
|
|
Mrs(MrsArgs),
|
|
|
|
/// List notes from discussions
|
|
#[command(visible_alias = "note", help_heading = "Query")]
|
|
Notes(NotesArgs),
|
|
|
|
/// Search indexed documents
|
|
#[command(visible_alias = "find", alias = "query", help_heading = "Query")]
|
|
Search(SearchArgs),
|
|
|
|
/// Count entities in local database
|
|
#[command(help_heading = "Query")]
|
|
Count(CountArgs),
|
|
|
|
// ── Intelligence ───────────────────────────────────────
|
|
/// Show a chronological timeline of events matching a query
|
|
#[command(help_heading = "Intelligence")]
|
|
Timeline(TimelineArgs),
|
|
|
|
/// People intelligence: experts, workload, active discussions, overlap
|
|
#[command(help_heading = "Intelligence")]
|
|
Who(WhoArgs),
|
|
|
|
/// Personal work dashboard: open issues, authored/reviewing MRs, activity
|
|
#[command(help_heading = "Intelligence")]
|
|
Me(MeArgs),
|
|
|
|
// ── File Analysis ──────────────────────────────────────
|
|
/// Trace why code was introduced: file -> MR -> issue -> discussion
|
|
#[command(help_heading = "File Analysis")]
|
|
Trace(TraceArgs),
|
|
|
|
/// Show MRs that touched a file, with linked discussions
|
|
#[command(name = "file-history", help_heading = "File Analysis")]
|
|
FileHistory(FileHistoryArgs),
|
|
|
|
/// Find semantically related entities via vector search
|
|
#[command(help_heading = "File Analysis", ...)]
|
|
Related { ... },
|
|
|
|
/// Detect discussion divergence from original intent
|
|
#[command(help_heading = "File Analysis", ...)]
|
|
Drift { ... },
|
|
|
|
// ── Data Pipeline ──────────────────────────────────────
|
|
/// Run full sync pipeline: ingest -> generate-docs -> embed
|
|
#[command(help_heading = "Data Pipeline")]
|
|
Sync(SyncArgs),
|
|
|
|
/// Ingest data from GitLab
|
|
#[command(help_heading = "Data Pipeline")]
|
|
Ingest(IngestArgs),
|
|
|
|
/// Generate searchable documents from ingested data
|
|
#[command(name = "generate-docs", help_heading = "Data Pipeline")]
|
|
GenerateDocs(GenerateDocsArgs),
|
|
|
|
/// Generate vector embeddings for documents via Ollama
|
|
#[command(help_heading = "Data Pipeline")]
|
|
Embed(EmbedArgs),
|
|
|
|
// ── System ─────────────────────────────────────────────
|
|
// (init, status, health, doctor, stats, auth, token, migrate, cron,
|
|
// completions, robot-docs, version -- all get help_heading = "System")
|
|
}
|
|
```
|
|
|
|
**Verification:**
|
|
- `lore --help` shows grouped output
|
|
- All existing commands still work identically
|
|
- `lore robot-docs` output unchanged (robot-docs is hand-crafted, not derived from clap)
|
|
|
|
**Files touched:** `src/cli/mod.rs` only
|
|
|
|
---
|
|
|
|
### P3: Singular/Plural Entity Type Fix
|
|
|
|
**Goal:** Accept both `issue`/`issues`, `mr`/`mrs` everywhere entity types are value-parsed.
|
|
|
|
**File:** `src/cli/args.rs`
|
|
|
|
**Change 1 -- `CountArgs.entity` (line 819):**
|
|
```rust
|
|
// BEFORE:
|
|
#[arg(value_parser = ["issues", "mrs", "discussions", "notes", "events"])]
|
|
pub entity: String,
|
|
|
|
// AFTER:
|
|
#[arg(value_parser = ["issue", "issues", "mr", "mrs", "discussion", "discussions", "note", "notes", "event", "events"])]
|
|
pub entity: String,
|
|
```
|
|
|
|
**File:** `src/cli/args.rs`
|
|
|
|
**Change 2 -- `SearchArgs.source_type` (line 369):**
|
|
```rust
|
|
// BEFORE:
|
|
#[arg(long = "type", value_parser = ["issue", "mr", "discussion", "note"], ...)]
|
|
pub source_type: Option<String>,
|
|
|
|
// AFTER:
|
|
#[arg(long = "type", value_parser = ["issue", "issues", "mr", "mrs", "discussion", "discussions", "note", "notes"], ...)]
|
|
pub source_type: Option<String>,
|
|
```
|
|
|
|
**File:** `src/cli/mod.rs`
|
|
|
|
**Change 3 -- `Drift.entity_type` (line 287):**
|
|
```rust
|
|
// BEFORE:
|
|
#[arg(value_parser = ["issues"])]
|
|
pub entity_type: String,
|
|
|
|
// AFTER:
|
|
#[arg(value_parser = ["issue", "issues"])]
|
|
pub entity_type: String,
|
|
```
|
|
|
|
**Normalization layer:** In the handlers that consume these values, normalize to the canonical form (plural for entity names, singular for source_type) so downstream code doesn't need changes:
|
|
|
|
**File:** `src/app/handlers.rs`
|
|
|
|
In `handle_count` (~line 409): Normalize entity string before passing to `run_count`:
|
|
```rust
|
|
let entity = match args.entity.as_str() {
|
|
"issue" => "issues",
|
|
"mr" => "mrs",
|
|
"discussion" => "discussions",
|
|
"note" => "notes",
|
|
"event" => "events",
|
|
other => other,
|
|
};
|
|
```
|
|
|
|
In `handle_search` (search handler): Normalize source_type:
|
|
```rust
|
|
let source_type = args.source_type.as_deref().map(|t| match t {
|
|
"issues" => "issue",
|
|
"mrs" => "mr",
|
|
"discussions" => "discussion",
|
|
"notes" => "note",
|
|
other => other,
|
|
});
|
|
```
|
|
|
|
In `handle_drift` (~line 225): Normalize entity_type:
|
|
```rust
|
|
let entity_type = if entity_type == "issue" { "issues" } else { &entity_type };
|
|
```
|
|
|
|
**Verification:**
|
|
- `lore count issue` works (same as `lore count issues`)
|
|
- `lore search --type issues 'foo'` works (same as `--type issue`)
|
|
- `lore drift issue 42` works (same as `drift issues 42`)
|
|
- All existing invocations unchanged
|
|
|
|
**Files touched:** `src/cli/args.rs`, `src/cli/mod.rs`, `src/app/handlers.rs`
|
|
|
|
---
|
|
|
|
### P5: Fix `-f` Short Flag Collision
|
|
|
|
**Goal:** Remove `-f` shorthand from `count --for` so `-f` consistently means `--force` across the CLI.
|
|
|
|
**File:** `src/cli/args.rs` (line 823)
|
|
|
|
```rust
|
|
// BEFORE:
|
|
#[arg(short = 'f', long = "for", value_parser = ["issue", "mr"])]
|
|
pub for_entity: Option<String>,
|
|
|
|
// AFTER:
|
|
#[arg(long = "for", value_parser = ["issue", "mr"])]
|
|
pub for_entity: Option<String>,
|
|
```
|
|
|
|
**Also update the value_parser to accept both forms** (while we're here):
|
|
```rust
|
|
#[arg(long = "for", value_parser = ["issue", "issues", "mr", "mrs"])]
|
|
pub for_entity: Option<String>,
|
|
```
|
|
|
|
And normalize in `handle_count`:
|
|
```rust
|
|
let for_entity = args.for_entity.as_deref().map(|f| match f {
|
|
"issues" => "issue",
|
|
"mrs" => "mr",
|
|
other => other,
|
|
});
|
|
```
|
|
|
|
**File:** `src/app/robot_docs.rs` (line 173) -- update the robot-docs entry:
|
|
```rust
|
|
// BEFORE:
|
|
"flags": ["<entity: issues|mrs|discussions|notes|events>", "-f/--for <issue|mr>"],
|
|
|
|
// AFTER:
|
|
"flags": ["<entity: issues|mrs|discussions|notes|events>", "--for <issue|mr>"],
|
|
```
|
|
|
|
**Verification:**
|
|
- `lore count notes --for mr` still works
|
|
- `lore count notes -f mr` now fails with a clear error (unknown flag `-f`)
|
|
- `lore ingest -f` still works (means `--force`)
|
|
|
|
**Files touched:** `src/cli/args.rs`, `src/app/robot_docs.rs`
|
|
|
|
---
|
|
|
|
### P9: Consistent `--open` Short Flag on `notes`
|
|
|
|
**Goal:** Add `-o` shorthand to `notes --open`, matching `issues` and `mrs`.
|
|
|
|
**File:** `src/cli/args.rs` (line 292)
|
|
|
|
```rust
|
|
// BEFORE:
|
|
#[arg(long, help_heading = "Actions")]
|
|
pub open: bool,
|
|
|
|
// AFTER:
|
|
#[arg(short = 'o', long, help_heading = "Actions", overrides_with = "no_open")]
|
|
pub open: bool,
|
|
|
|
#[arg(long = "no-open", hide = true, overrides_with = "open")]
|
|
pub no_open: bool,
|
|
```
|
|
|
|
**Verification:**
|
|
- `lore notes -o` opens first result in browser
|
|
- Matches behavior of `lore issues -o` and `lore mrs -o`
|
|
|
|
**Files touched:** `src/cli/args.rs`
|
|
|
|
---
|
|
|
|
### Phase 1 Commit Summary
|
|
|
|
**Files modified:**
|
|
1. `src/cli/mod.rs` -- help_heading on all Commands variants + drift value_parser
|
|
2. `src/cli/args.rs` -- singular/plural value_parsers, remove `-f` from count, add `-o` to notes
|
|
3. `src/app/handlers.rs` -- normalization of entity/source_type strings
|
|
4. `src/app/robot_docs.rs` -- update count flags documentation
|
|
|
|
**Test plan:**
|
|
```bash
|
|
cargo check --all-targets
|
|
cargo clippy --all-targets -- -D warnings
|
|
cargo fmt --check
|
|
cargo test
|
|
lore --help # Verify grouped output
|
|
lore count issue # Verify singular accepted
|
|
lore search --type issues 'x' # Verify plural accepted
|
|
lore drift issue 42 # Verify singular accepted
|
|
lore notes -o # Verify short flag works
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 2: Renames and Merges (2-3 commits)
|
|
|
|
These changes rename commands and merge overlapping ones. Hidden aliases preserve backward compatibility.
|
|
|
|
### P2: Rename `stats` -> `index`
|
|
|
|
**Goal:** Eliminate `status`/`stats`/`stat` confusion. `stats` becomes `index`.
|
|
|
|
**File:** `src/cli/mod.rs`
|
|
|
|
```rust
|
|
// BEFORE:
|
|
/// Show document and index statistics
|
|
#[command(visible_alias = "stat", help_heading = "System")]
|
|
Stats(StatsArgs),
|
|
|
|
// AFTER:
|
|
/// Show document and index statistics
|
|
#[command(visible_alias = "idx", alias = "stats", alias = "stat", help_heading = "System")]
|
|
Index(StatsArgs),
|
|
```
|
|
|
|
Note: `alias = "stats"` and `alias = "stat"` are hidden aliases (not `visible_alias`) -- old invocations still work, but `--help` shows `index`.
|
|
|
|
**File:** `src/main.rs` (line 257)
|
|
|
|
```rust
|
|
// BEFORE:
|
|
Some(Commands::Stats(args)) => handle_stats(cli.config.as_deref(), args, robot_mode).await,
|
|
|
|
// AFTER:
|
|
Some(Commands::Index(args)) => handle_stats(cli.config.as_deref(), args, robot_mode).await,
|
|
```
|
|
|
|
**File:** `src/app/robot_docs.rs` (line 181)
|
|
|
|
```rust
|
|
// BEFORE:
|
|
"stats": {
|
|
"description": "Show document and index statistics",
|
|
...
|
|
|
|
// AFTER:
|
|
"index": {
|
|
"description": "Show document and index statistics (formerly 'stats')",
|
|
...
|
|
```
|
|
|
|
Also update references in:
|
|
- `robot_docs.rs` quick_start.lore_exclusive array (line 415): `"stats: Database statistics..."` -> `"index: Database statistics..."`
|
|
- `robot_docs.rs` aliases.deprecated_commands: add `"stats": "index"`, `"stat": "index"`
|
|
|
|
**File:** `src/cli/autocorrect.rs`
|
|
|
|
Update `CANONICAL_SUBCOMMANDS` (line 366-area):
|
|
```rust
|
|
// Replace "stats" with "index" in the canonical list
|
|
// Add ("stats", "index") and ("stat", "index") to SUBCOMMAND_ALIASES
|
|
```
|
|
|
|
Update `COMMAND_FLAGS` (line 166-area):
|
|
```rust
|
|
// BEFORE:
|
|
("stats", &["--check", ...]),
|
|
|
|
// AFTER:
|
|
("index", &["--check", ...]),
|
|
```
|
|
|
|
**File:** `src/cli/robot.rs` -- update `expand_fields_preset` if any preset key is `"stats"` (currently no stats preset, so no change needed).
|
|
|
|
**Verification:**
|
|
- `lore index` works (shows document/index stats)
|
|
- `lore stats` still works (hidden alias)
|
|
- `lore stat` still works (hidden alias)
|
|
- `lore index --check` works
|
|
- `lore --help` shows `index` in System group, not `stats`
|
|
- `lore robot-docs` shows `index` key in commands map
|
|
|
|
**Files touched:** `src/cli/mod.rs`, `src/main.rs`, `src/app/robot_docs.rs`, `src/cli/autocorrect.rs`
|
|
|
|
---
|
|
|
|
### P4: Merge `health` into `doctor`
|
|
|
|
**Goal:** One diagnostic command (`doctor`) with a `--quick` flag for the pre-flight check that `health` currently provides.
|
|
|
|
**File:** `src/cli/mod.rs`
|
|
|
|
```rust
|
|
// BEFORE:
|
|
/// Quick health check: config, database, schema version
|
|
#[command(after_help = "...")]
|
|
Health,
|
|
|
|
/// Check environment health
|
|
#[command(after_help = "...")]
|
|
Doctor,
|
|
|
|
// AFTER:
|
|
// Remove Health variant entirely. Add hidden alias:
|
|
/// Check environment health (--quick for fast pre-flight)
|
|
#[command(
|
|
after_help = "...",
|
|
alias = "health", // hidden backward compat
|
|
help_heading = "System"
|
|
)]
|
|
Doctor {
|
|
/// Fast pre-flight check only (config, DB, schema). Exit 0 = healthy.
|
|
#[arg(long)]
|
|
quick: bool,
|
|
},
|
|
```
|
|
|
|
**File:** `src/main.rs`
|
|
|
|
```rust
|
|
// BEFORE:
|
|
Some(Commands::Doctor) => handle_doctor(cli.config.as_deref(), robot_mode).await,
|
|
...
|
|
Some(Commands::Health) => handle_health(cli.config.as_deref(), robot_mode).await,
|
|
|
|
// AFTER:
|
|
Some(Commands::Doctor { quick }) => {
|
|
if quick {
|
|
handle_health(cli.config.as_deref(), robot_mode).await
|
|
} else {
|
|
handle_doctor(cli.config.as_deref(), robot_mode).await
|
|
}
|
|
}
|
|
// Health variant removed from enum, so no separate match arm
|
|
```
|
|
|
|
**File:** `src/app/robot_docs.rs`
|
|
|
|
Merge the `health` and `doctor` entries:
|
|
```rust
|
|
"doctor": {
|
|
"description": "Environment health check. Use --quick for fast pre-flight (exit 0 = healthy, 19 = unhealthy).",
|
|
"flags": ["--quick"],
|
|
"example": "lore --robot doctor",
|
|
"notes": {
|
|
"quick_mode": "lore --robot doctor --quick — fast pre-flight check (formerly 'lore health'). Only checks config, DB, schema version. Returns exit 19 on failure.",
|
|
"full_mode": "lore --robot doctor — full diagnostic: config, auth, database, Ollama"
|
|
},
|
|
"response_schema": {
|
|
"full": { ... }, // current doctor schema
|
|
"quick": { ... } // current health schema
|
|
}
|
|
}
|
|
```
|
|
|
|
Remove the standalone `health` entry from the commands map.
|
|
|
|
**File:** `src/cli/autocorrect.rs`
|
|
|
|
- Remove `"health"` from `CANONICAL_SUBCOMMANDS` (clap's `alias` handles it)
|
|
- Or keep it -- since clap treats aliases as valid subcommands, the autocorrect system will still resolve typos like `"helth"` to `"health"` which clap then maps to `doctor`. Either way works.
|
|
|
|
**File:** `src/app/robot_docs.rs` -- update `workflows.pre_flight`:
|
|
```rust
|
|
"pre_flight": [
|
|
"lore --robot doctor --quick"
|
|
],
|
|
```
|
|
|
|
Add to aliases.deprecated_commands:
|
|
```rust
|
|
"health": "doctor --quick"
|
|
```
|
|
|
|
**Verification:**
|
|
- `lore doctor` runs full diagnostic (unchanged behavior)
|
|
- `lore doctor --quick` runs fast pre-flight (exit 0/19)
|
|
- `lore health` still works (hidden alias, runs `doctor --quick`)
|
|
- `lore --help` shows only `doctor` in System group
|
|
- `lore robot-docs` shows merged entry
|
|
|
|
**Files touched:** `src/cli/mod.rs`, `src/main.rs`, `src/app/robot_docs.rs`, `src/cli/autocorrect.rs`
|
|
|
|
**Important edge case:** `lore health` via the hidden alias will invoke `Doctor { quick: false }` unless we handle it specially. Two options:
|
|
|
|
**Option A (simpler):** Instead of making `health` an alias of `doctor`, keep both variants but hide `Health`:
|
|
```rust
|
|
#[command(hide = true, help_heading = "System")]
|
|
Health,
|
|
```
|
|
Then in `main.rs`, `Commands::Health` maps to `handle_health()` as before. This is less clean but zero-risk.
|
|
|
|
**Option B (cleaner):** In the autocorrect layer, rewrite `health` -> `doctor --quick` before clap parsing:
|
|
```rust
|
|
// In SUBCOMMAND_ALIASES or a new pre-clap rewrite:
|
|
("health", "doctor"), // plus inject "--quick" flag
|
|
```
|
|
This requires a small enhancement to autocorrect to support flag injection during alias resolution.
|
|
|
|
**Recommendation:** Use Option A for initial implementation. It's one line (`hide = true`) and achieves the goal of removing `health` from `--help` while preserving full backward compatibility. The `doctor --quick` flag is additive.
|
|
|
|
---
|
|
|
|
### P7: Hide Pipeline Sub-stages
|
|
|
|
**Goal:** Remove `ingest`, `generate-docs`, `embed` from `--help` while keeping them fully functional.
|
|
|
|
**File:** `src/cli/mod.rs`
|
|
|
|
```rust
|
|
// Add hide = true to each:
|
|
|
|
/// Ingest data from GitLab
|
|
#[command(hide = true)]
|
|
Ingest(IngestArgs),
|
|
|
|
/// Generate searchable documents from ingested data
|
|
#[command(name = "generate-docs", hide = true)]
|
|
GenerateDocs(GenerateDocsArgs),
|
|
|
|
/// Generate vector embeddings for documents via Ollama
|
|
#[command(hide = true)]
|
|
Embed(EmbedArgs),
|
|
```
|
|
|
|
**File:** `src/cli/mod.rs` -- Update `Sync` help text to mention the individual stage commands:
|
|
|
|
```rust
|
|
/// Run full sync pipeline: ingest -> generate-docs -> embed
|
|
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
|
lore sync # Full pipeline: ingest + docs + embed
|
|
lore sync --no-embed # Skip embedding step
|
|
...
|
|
|
|
\x1b[1mIndividual stages:\x1b[0m
|
|
lore ingest # Fetch from GitLab only
|
|
lore generate-docs # Rebuild documents only
|
|
lore embed # Re-embed only",
|
|
help_heading = "Data Pipeline"
|
|
)]
|
|
Sync(SyncArgs),
|
|
```
|
|
|
|
**File:** `src/app/robot_docs.rs` -- Add a `"hidden": true` field to the ingest/generate-docs/embed entries so agents know these are secondary:
|
|
```rust
|
|
"ingest": {
|
|
"hidden": true,
|
|
"description": "Sync data from GitLab (prefer 'sync' for full pipeline)",
|
|
...
|
|
```
|
|
|
|
**Verification:**
|
|
- `lore --help` no longer shows ingest, generate-docs, embed
|
|
- `lore ingest`, `lore generate-docs`, `lore embed` all still work
|
|
- `lore sync --help` mentions individual stage commands
|
|
- `lore robot-docs` still includes all three (with `hidden: true`)
|
|
|
|
**Files touched:** `src/cli/mod.rs`, `src/app/robot_docs.rs`
|
|
|
|
---
|
|
|
|
### Phase 2 Commit Summary
|
|
|
|
**Commit A: Rename `stats` -> `index`**
|
|
- `src/cli/mod.rs`, `src/main.rs`, `src/app/robot_docs.rs`, `src/cli/autocorrect.rs`
|
|
|
|
**Commit B: Merge `health` into `doctor`, hide pipeline stages**
|
|
- `src/cli/mod.rs`, `src/main.rs`, `src/app/robot_docs.rs`, `src/cli/autocorrect.rs`
|
|
|
|
**Test plan:**
|
|
```bash
|
|
cargo check --all-targets
|
|
cargo clippy --all-targets -- -D warnings
|
|
cargo fmt --check
|
|
cargo test
|
|
|
|
# Rename verification
|
|
lore index # Works (new name)
|
|
lore stats # Works (hidden alias)
|
|
lore index --check # Works
|
|
|
|
# Doctor merge verification
|
|
lore doctor # Full diagnostic
|
|
lore doctor --quick # Fast pre-flight
|
|
lore health # Still works (hidden)
|
|
|
|
# Hidden stages verification
|
|
lore --help # ingest/generate-docs/embed gone
|
|
lore ingest # Still works
|
|
lore sync --help # Mentions individual stages
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 3: Structural Consolidation (requires careful design)
|
|
|
|
These changes merge or absorb commands. More effort, more testing, but the biggest UX wins.
|
|
|
|
### P6: Consolidate `file-history` into `trace`
|
|
|
|
**Goal:** `trace` absorbs `file-history`. One command for file-centric intelligence.
|
|
|
|
**Approach:** Add `--mrs-only` flag to `trace`. When set, output matches `file-history` format (flat MR list, no issue/discussion linking). `file-history` becomes a hidden alias.
|
|
|
|
**File:** `src/cli/args.rs` -- Add flag to `TraceArgs`:
|
|
|
|
```rust
|
|
pub struct TraceArgs {
|
|
pub path: String,
|
|
|
|
#[arg(short = 'p', long, help_heading = "Filters")]
|
|
pub project: Option<String>,
|
|
|
|
#[arg(long, help_heading = "Output")]
|
|
pub discussions: bool,
|
|
|
|
#[arg(long = "no-follow-renames", help_heading = "Filters")]
|
|
pub no_follow_renames: bool,
|
|
|
|
#[arg(short = 'n', long = "limit", default_value = "20", help_heading = "Output")]
|
|
pub limit: usize,
|
|
|
|
// NEW: absorb file-history behavior
|
|
/// Show only MR list without issue/discussion linking (file-history mode)
|
|
#[arg(long = "mrs-only", help_heading = "Output")]
|
|
pub mrs_only: bool,
|
|
|
|
/// Only show merged MRs (file-history mode)
|
|
#[arg(long, help_heading = "Filters")]
|
|
pub merged: bool,
|
|
}
|
|
```
|
|
|
|
**File:** `src/cli/mod.rs` -- Hide `FileHistory`:
|
|
|
|
```rust
|
|
/// Show MRs that touched a file, with linked discussions
|
|
#[command(name = "file-history", hide = true, help_heading = "File Analysis")]
|
|
FileHistory(FileHistoryArgs),
|
|
```
|
|
|
|
**File:** `src/app/handlers.rs` -- Route `trace --mrs-only` to the file-history handler:
|
|
|
|
```rust
|
|
fn handle_trace(
|
|
config_override: Option<&str>,
|
|
args: TraceArgs,
|
|
robot_mode: bool,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
if args.mrs_only {
|
|
// Delegate to file-history handler
|
|
let fh_args = FileHistoryArgs {
|
|
path: args.path,
|
|
project: args.project,
|
|
discussions: args.discussions,
|
|
no_follow_renames: args.no_follow_renames,
|
|
merged: args.merged,
|
|
limit: args.limit,
|
|
};
|
|
return handle_file_history(config_override, fh_args, robot_mode);
|
|
}
|
|
// ... existing trace logic ...
|
|
}
|
|
```
|
|
|
|
**File:** `src/app/robot_docs.rs` -- Update trace entry, mark file-history as deprecated:
|
|
|
|
```rust
|
|
"trace": {
|
|
"description": "Trace why code was introduced: file -> MR -> issue -> discussion. Use --mrs-only for flat MR listing.",
|
|
"flags": ["<path>", "-p/--project", "--discussions", "--no-follow-renames", "-n/--limit", "--mrs-only", "--merged"],
|
|
...
|
|
},
|
|
"file-history": {
|
|
"hidden": true,
|
|
"deprecated": "Use 'trace --mrs-only' instead",
|
|
...
|
|
}
|
|
```
|
|
|
|
**Verification:**
|
|
- `lore trace src/main.rs` works unchanged
|
|
- `lore trace src/main.rs --mrs-only` produces file-history output
|
|
- `lore trace src/main.rs --mrs-only --merged` filters to merged MRs
|
|
- `lore file-history src/main.rs` still works (hidden command)
|
|
- `lore --help` shows only `trace` in File Analysis group
|
|
|
|
**Files touched:** `src/cli/args.rs`, `src/cli/mod.rs`, `src/app/handlers.rs`, `src/app/robot_docs.rs`
|
|
|
|
---
|
|
|
|
### P8: Make `count` a Flag on Entity Commands
|
|
|
|
**Goal:** `lore issues --count` replaces `lore count issues`. Standalone `count` becomes hidden.
|
|
|
|
**File:** `src/cli/args.rs` -- Add `--count` to `IssuesArgs`, `MrsArgs`, `NotesArgs`:
|
|
|
|
```rust
|
|
// In IssuesArgs:
|
|
/// Show count only (no listing)
|
|
#[arg(long, help_heading = "Output", conflicts_with_all = ["iid", "open"])]
|
|
pub count: bool,
|
|
|
|
// In MrsArgs:
|
|
/// Show count only (no listing)
|
|
#[arg(long, help_heading = "Output", conflicts_with_all = ["iid", "open"])]
|
|
pub count: bool,
|
|
|
|
// In NotesArgs:
|
|
/// Show count only (no listing)
|
|
#[arg(long, help_heading = "Output", conflicts_with = "open")]
|
|
pub count: bool,
|
|
```
|
|
|
|
**File:** `src/app/handlers.rs` -- In `handle_issues`, `handle_mrs`, `handle_notes`, check the count flag early:
|
|
|
|
```rust
|
|
// In handle_issues (pseudocode):
|
|
if args.count {
|
|
let count_args = CountArgs { entity: "issues".to_string(), for_entity: None };
|
|
return handle_count(config_override, count_args, robot_mode).await;
|
|
}
|
|
```
|
|
|
|
**File:** `src/cli/mod.rs` -- Hide `Count`:
|
|
|
|
```rust
|
|
/// Count entities in local database
|
|
#[command(hide = true, help_heading = "Query")]
|
|
Count(CountArgs),
|
|
```
|
|
|
|
**File:** `src/app/robot_docs.rs` -- Mark count as hidden, add `--count` documentation to issues/mrs/notes entries.
|
|
|
|
**Verification:**
|
|
- `lore issues --count` returns issue count
|
|
- `lore mrs --count` returns MR count
|
|
- `lore notes --count` returns note count
|
|
- `lore count issues` still works (hidden)
|
|
- `lore count discussions --for mr` still works (no equivalent in the new pattern -- discussions/events/references still need the standalone `count` command)
|
|
|
|
**Important note:** `count` supports entity types that don't have their own command (discussions, events, references). The standalone `count` must remain functional (just hidden). The `--count` flag on `issues`/`mrs`/`notes` handles the common cases only.
|
|
|
|
**Files touched:** `src/cli/args.rs`, `src/cli/mod.rs`, `src/app/handlers.rs`, `src/app/robot_docs.rs`
|
|
|
|
---
|
|
|
|
### P10: Add `--sort` to `search`
|
|
|
|
**Goal:** Allow sorting search results by score, created date, or updated date.
|
|
|
|
**File:** `src/cli/args.rs` -- Add to `SearchArgs`:
|
|
|
|
```rust
|
|
/// Sort results by field (score is default for ranked search)
|
|
#[arg(long, value_parser = ["score", "created", "updated"], default_value = "score", help_heading = "Sorting")]
|
|
pub sort: String,
|
|
|
|
/// Sort ascending (default: descending)
|
|
#[arg(long, help_heading = "Sorting", overrides_with = "no_asc")]
|
|
pub asc: bool,
|
|
|
|
#[arg(long = "no-asc", hide = true, overrides_with = "asc")]
|
|
pub no_asc: bool,
|
|
```
|
|
|
|
**File:** `src/cli/commands/search.rs` -- Thread the sort parameter through to the search query.
|
|
|
|
The search function currently returns results sorted by score. When `--sort created` or `--sort updated` is specified, apply an `ORDER BY` clause to the final result set.
|
|
|
|
**File:** `src/app/robot_docs.rs` -- Add `--sort` and `--asc` to the search command's flags list.
|
|
|
|
**Verification:**
|
|
- `lore search 'auth' --sort score` (default, unchanged)
|
|
- `lore search 'auth' --sort created --asc` (oldest first)
|
|
- `lore search 'auth' --sort updated` (most recently updated first)
|
|
|
|
**Files touched:** `src/cli/args.rs`, `src/cli/commands/search.rs`, `src/app/robot_docs.rs`
|
|
|
|
---
|
|
|
|
### Phase 3 Commit Summary
|
|
|
|
**Commit C: Consolidate file-history into trace**
|
|
- `src/cli/args.rs`, `src/cli/mod.rs`, `src/app/handlers.rs`, `src/app/robot_docs.rs`
|
|
|
|
**Commit D: Add `--count` flag to entity commands**
|
|
- `src/cli/args.rs`, `src/cli/mod.rs`, `src/app/handlers.rs`, `src/app/robot_docs.rs`
|
|
|
|
**Commit E: Add `--sort` to search**
|
|
- `src/cli/args.rs`, `src/cli/commands/search.rs`, `src/app/robot_docs.rs`
|
|
|
|
**Test plan:**
|
|
```bash
|
|
cargo check --all-targets
|
|
cargo clippy --all-targets -- -D warnings
|
|
cargo fmt --check
|
|
cargo test
|
|
|
|
# trace consolidation
|
|
lore trace src/main.rs --mrs-only
|
|
lore trace src/main.rs --mrs-only --merged --discussions
|
|
lore file-history src/main.rs # backward compat
|
|
|
|
# count flag
|
|
lore issues --count
|
|
lore mrs --count -s opened
|
|
lore notes --count --for-issue 42
|
|
lore count discussions --for mr # still works
|
|
|
|
# search sort
|
|
lore search 'auth' --sort created --asc
|
|
```
|
|
|
|
---
|
|
|
|
## Documentation Updates
|
|
|
|
After all implementation is complete:
|
|
|
|
### CLAUDE.md / AGENTS.md
|
|
|
|
Update the robot mode command reference to reflect:
|
|
- `stats` -> `index` (with note that `stats` is a hidden alias)
|
|
- `health` -> `doctor --quick` (with note that `health` is a hidden alias)
|
|
- Remove `ingest`, `generate-docs`, `embed` from the primary command table (mention as "hidden, use `sync`")
|
|
- Remove `file-history` from primary table (mention as "hidden, use `trace --mrs-only`")
|
|
- Add `--count` flag to issues/mrs/notes documentation
|
|
- Add `--sort` flag to search documentation
|
|
- Add `--mrs-only` and `--merged` flags to trace documentation
|
|
|
|
### robot-docs Self-Discovery
|
|
|
|
The `robot_docs.rs` changes above handle this. Key points:
|
|
- New `"hidden": true` field on deprecated/hidden commands
|
|
- Updated descriptions mentioning canonical alternatives
|
|
- Updated flags lists
|
|
- Updated workflows section
|
|
|
|
---
|
|
|
|
## File Impact Summary
|
|
|
|
| File | Phase 1 | Phase 2 | Phase 3 | Total Changes |
|
|
|------|---------|---------|---------|---------------|
|
|
| `src/cli/mod.rs` | help_heading, drift value_parser | stats->index rename, hide health, hide pipeline stages | hide file-history, hide count | 4 passes |
|
|
| `src/cli/args.rs` | singular/plural, remove `-f`, add `-o` | — | `--mrs-only`/`--merged` on trace, `--count` on entities, `--sort` on search | 2 passes |
|
|
| `src/app/handlers.rs` | normalize entity strings | route doctor --quick | trace mrs-only delegation, count flag routing | 3 passes |
|
|
| `src/app/robot_docs.rs` | update count flags | rename stats->index, merge health+doctor, add hidden field | update trace, file-history, count, search entries | 3 passes |
|
|
| `src/cli/autocorrect.rs` | — | update CANONICAL_SUBCOMMANDS, SUBCOMMAND_ALIASES, COMMAND_FLAGS | — | 1 pass |
|
|
| `src/main.rs` | — | stats->index variant rename, doctor variant change | — | 1 pass |
|
|
| `src/cli/commands/search.rs` | — | — | sort parameter threading | 1 pass |
|
|
|
|
---
|
|
|
|
## Before / After Summary
|
|
|
|
### Command Count
|
|
|
|
| Metric | Before | After | Delta |
|
|
|--------|--------|-------|-------|
|
|
| Visible top-level commands | 29 | 21 | -8 (-28%) |
|
|
| Hidden commands (functional) | 4 | 12 | +8 (absorbed) |
|
|
| Stub/unimplemented commands | 2 | 2 | 0 |
|
|
| Total functional commands | 33 | 33 | 0 (nothing lost) |
|
|
|
|
### `lore --help` Output
|
|
|
|
**Before (29 commands, flat list, ~50 lines of commands):**
|
|
```
|
|
Commands:
|
|
issues List or show issues [aliases: issue]
|
|
mrs List or show merge requests [aliases: mr]
|
|
notes List notes from discussions [aliases: note]
|
|
ingest Ingest data from GitLab
|
|
count Count entities in local database
|
|
status Show sync state [aliases: st]
|
|
auth Verify GitLab authentication
|
|
doctor Check environment health
|
|
version Show version information
|
|
init Initialize configuration and database
|
|
search Search indexed documents [aliases: find]
|
|
stats Show document and index statistics [aliases: stat]
|
|
generate-docs Generate searchable documents from ingested data
|
|
embed Generate vector embeddings for documents via Ollama
|
|
sync Run full sync pipeline: ingest -> generate-docs -> embed
|
|
migrate Run pending database migrations
|
|
health Quick health check: config, database, schema version
|
|
robot-docs Machine-readable command manifest for agent self-discovery
|
|
completions Generate shell completions
|
|
timeline Show a chronological timeline of events matching a query
|
|
who People intelligence: experts, workload, active discussions, overlap
|
|
me Personal work dashboard: open issues, authored/reviewing MRs, activity
|
|
file-history Show MRs that touched a file, with linked discussions
|
|
trace Trace why code was introduced: file -> MR -> issue -> discussion
|
|
drift Detect discussion divergence from original intent
|
|
related Find semantically related entities via vector search
|
|
cron Manage cron-based automatic syncing
|
|
token Manage stored GitLab token
|
|
help Print this message or the help of the given subcommand(s)
|
|
```
|
|
|
|
**After (21 commands, grouped, ~35 lines of commands):**
|
|
```
|
|
Query:
|
|
issues List or show issues [aliases: issue]
|
|
mrs List or show merge requests [aliases: mr]
|
|
notes List notes from discussions [aliases: note]
|
|
search Search indexed documents [aliases: find]
|
|
|
|
Intelligence:
|
|
timeline Chronological timeline of events
|
|
who People intelligence: experts, workload, overlap
|
|
me Personal work dashboard
|
|
|
|
File Analysis:
|
|
trace Trace code provenance / file history
|
|
related Find semantically related entities
|
|
drift Detect discussion divergence
|
|
|
|
Data Pipeline:
|
|
sync Run full sync pipeline
|
|
|
|
System:
|
|
init Initialize configuration and database
|
|
status Show sync state [aliases: st]
|
|
doctor Check environment health (--quick for pre-flight)
|
|
index Document and index statistics [aliases: idx]
|
|
auth Verify GitLab authentication
|
|
token Manage stored GitLab token
|
|
migrate Run pending database migrations
|
|
cron Manage automatic syncing
|
|
robot-docs Agent self-discovery manifest
|
|
completions Generate shell completions
|
|
version Show version information
|
|
```
|
|
|
|
### Flag Consistency
|
|
|
|
| Issue | Before | After |
|
|
|-------|--------|-------|
|
|
| `-f` collision (force vs for) | `ingest -f`=force, `count -f`=for | `-f` removed from count; `-f` = force everywhere |
|
|
| Singular/plural entity types | `count issues` but `search --type issue` | Both forms accepted everywhere |
|
|
| `notes --open` missing `-o` | `notes --open` (no shorthand) | `notes -o` works (matches issues/mrs) |
|
|
| `search` missing `--sort` | No sort override | `--sort score\|created\|updated` + `--asc` |
|
|
|
|
### Naming Confusion
|
|
|
|
| Before | After | Resolution |
|
|
|--------|-------|------------|
|
|
| `status` vs `stats` vs `stat` (3 names, 2 commands) | `status` + `index` (2 names, 2 commands) | Eliminated near-homonym collision |
|
|
| `health` vs `doctor` (2 commands, overlapping scope) | `doctor` + `doctor --quick` (1 command) | Progressive disclosure |
|
|
| `trace` vs `file-history` (2 commands, overlapping function) | `trace` + `trace --mrs-only` (1 command) | Superset absorbs subset |
|
|
|
|
### Robot Ergonomics
|
|
|
|
| Metric | Before | After |
|
|
|--------|--------|-------|
|
|
| Commands in robot-docs manifest | 29 | 21 visible + hidden section |
|
|
| Agent decision space for "system check" | 4 commands | 2 commands (status, doctor) |
|
|
| Agent decision space for "file query" | 3 commands + 2 who modes | 1 command (trace) + 2 who modes |
|
|
| Entity type parse errors from singular/plural | Common | Eliminated |
|
|
| Estimated token cost of robot-docs | Baseline | ~15% reduction (fewer entries, hidden flagged) |
|
|
|
|
### What Stays Exactly The Same
|
|
|
|
- All 33 functional commands remain callable (nothing is removed)
|
|
- All existing flags and their behavior are preserved
|
|
- All response schemas are unchanged
|
|
- All exit codes are unchanged
|
|
- The autocorrect system continues to work
|
|
- All hidden/deprecated commands emit their existing warnings
|
|
|
|
### What Breaks (Intentional)
|
|
|
|
- `lore count -f mr` (the `-f` shorthand) -- must use `--for` instead
|
|
- `lore --help` layout changes (commands are grouped, 8 commands hidden)
|
|
- `lore robot-docs` output changes (new `hidden` field, renamed keys)
|
|
- Any scripts parsing `--help` text (but `robot-docs` is the stable contract)
|