docs(cli): add command restructure audit and implementation plan
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.
This commit is contained in:
389
command-restructure/CLI_AUDIT.md
Normal file
389
command-restructure/CLI_AUDIT.md
Normal file
@@ -0,0 +1,389 @@
|
||||
# Gitlore CLI Command Audit
|
||||
|
||||
## 1. Full Command Inventory
|
||||
|
||||
**29 visible + 4 hidden + 2 stub = 35 total command surface**
|
||||
|
||||
| # | Command | Aliases | Args | Flags | Purpose |
|
||||
|---|---------|---------|------|-------|---------|
|
||||
| 1 | `issues` | `issue` | `[IID]` | 15 | List/show issues |
|
||||
| 2 | `mrs` | `mr`, `merge-requests` | `[IID]` | 16 | List/show MRs |
|
||||
| 3 | `notes` | `note` | — | 16 | List notes |
|
||||
| 4 | `search` | `find`, `query` | `<QUERY>` | 13 | Hybrid FTS+vector search |
|
||||
| 5 | `timeline` | — | `<QUERY>` | 11 | Chronological event reconstruction |
|
||||
| 6 | `who` | — | `[TARGET]` | 16 | People intelligence (5 modes) |
|
||||
| 7 | `me` | — | — | 10 | Personal dashboard |
|
||||
| 8 | `file-history` | — | `<PATH>` | 6 | MRs that touched a file |
|
||||
| 9 | `trace` | — | `<PATH>` | 5 | file->MR->issue->discussion chain |
|
||||
| 10 | `drift` | — | `<TYPE> <IID>` | 3 | Discussion divergence detection |
|
||||
| 11 | `related` | — | `<QUERY_OR_TYPE> [IID]` | 3 | Semantic similarity |
|
||||
| 12 | `count` | — | `<ENTITY>` | 2 | Count entities |
|
||||
| 13 | `sync` | — | — | 14 | Full pipeline: ingest+docs+embed |
|
||||
| 14 | `ingest` | — | `[ENTITY]` | 5 | Fetch from GitLab API |
|
||||
| 15 | `generate-docs` | — | — | 2 | Build searchable documents |
|
||||
| 16 | `embed` | — | — | 2 | Generate vector embeddings |
|
||||
| 17 | `status` | `st` | — | 0 | Last sync times per project |
|
||||
| 18 | `health` | — | — | 0 | Quick pre-flight (exit code only) |
|
||||
| 19 | `doctor` | — | — | 0 | Full environment diagnostic |
|
||||
| 20 | `stats` | `stat` | — | 3 | Document/index statistics |
|
||||
| 21 | `init` | — | — | 6 | Setup config + database |
|
||||
| 22 | `auth` | — | — | 0 | Verify GitLab token |
|
||||
| 23 | `token` | — | subcommand | 1-2 | Token CRUD (set/show) |
|
||||
| 24 | `cron` | — | subcommand | 0-1 | Auto-sync scheduling |
|
||||
| 25 | `migrate` | — | — | 0 | Apply DB migrations |
|
||||
| 26 | `robot-docs` | — | — | 1 | Agent self-discovery manifest |
|
||||
| 27 | `completions` | — | `<SHELL>` | 0 | Shell completions |
|
||||
| 28 | `version` | — | — | 0 | Version info |
|
||||
| 29 | *help* | — | — | — | (clap built-in) |
|
||||
| | **Hidden/deprecated:** | | | | |
|
||||
| 30 | `list` | — | `<ENTITY>` | 14 | deprecated, use issues/mrs |
|
||||
| 31 | `show` | — | `<ENTITY> <IID>` | 1 | deprecated, use issues/mrs |
|
||||
| 32 | `auth-test` | — | — | 0 | deprecated, use auth |
|
||||
| 33 | `sync-status` | — | — | 0 | deprecated, use status |
|
||||
| 34 | `backup` | — | — | 0 | Stub (not implemented) |
|
||||
| 35 | `reset` | — | — | 1 | Stub (not implemented) |
|
||||
|
||||
---
|
||||
|
||||
## 2. Semantic Overlap Analysis
|
||||
|
||||
### Cluster A: "Is the system working?" (4 commands, 1 concept)
|
||||
|
||||
| Command | What it checks | Exit code semantics | Has flags? |
|
||||
|---------|---------------|---------------------|------------|
|
||||
| `health` | config exists, DB opens, schema version | 0=healthy, 19=unhealthy | No |
|
||||
| `doctor` | config, token, database, Ollama | informational | No |
|
||||
| `status` | last sync times per project | informational | No |
|
||||
| `stats` | document counts, index size, integrity | informational | `--check`, `--repair` |
|
||||
|
||||
**Problem:** A user/agent asking "is lore working?" must choose among four commands. `health` is a strict subset of `doctor`. `status` and `stats` are near-homonyms that answer different questions -- sync recency vs. index health. `count` (Cluster E) also overlaps with what `stats` reports.
|
||||
|
||||
**Cognitive cost:** High. The CLI literature (Clig.dev, Heroku CLI design guide, 12-factor CLI) consistently warns against >2 "status" commands. Users build a mental model of "the status command" -- when there are four, they pick wrong or give up.
|
||||
|
||||
**Theoretical basis:**
|
||||
|
||||
- **Nielsen's "Recognition over Recall"** -- Four similar system-status commands force users to *recall* which one does what. One command with progressive disclosure (flags for depth) lets them *recognize* the option they need. This is doubly important for LLM agents, which perform better with fewer top-level choices and compositional flags.
|
||||
|
||||
- **Fitts's Law for CLIs** -- Command discovery cost is proportional to list length. Each additional top-level command adds scanning time for humans and token cost for robots.
|
||||
|
||||
### Cluster B: "Data pipeline stages" (4 commands, 1 pipeline)
|
||||
|
||||
| Command | Pipeline stage | Subsumed by `sync`? |
|
||||
|---------|---------------|---------------------|
|
||||
| `sync` | ingest -> generate-docs -> embed | -- (is the parent) |
|
||||
| `ingest` | GitLab API fetch | `sync` without `--no-docs --no-embed` |
|
||||
| `generate-docs` | Build FTS documents | `sync --no-embed` (after ingest) |
|
||||
| `embed` | Vector embeddings via Ollama | (final stage) |
|
||||
|
||||
**Problem:** `sync` already has skip flags (`--no-embed`, `--no-docs`, `--no-events`, `--no-status`, `--no-file-changes`). The individual stage commands duplicate this with less control -- `ingest` has `--full`, `--force`, `--dry-run`, but `sync` also has all three.
|
||||
|
||||
The standalone commands exist for granular debugging, but in practice they're reached for <5% of the time. They inflate the help screen while `sync` handles 95% of use cases.
|
||||
|
||||
### Cluster C: "File-centric intelligence" (3 overlapping surfaces)
|
||||
|
||||
| Command | Input | Output | Key flags |
|
||||
|---------|-------|--------|-----------|
|
||||
| `file-history` | `<PATH>` | MRs that touched file | `-p`, `--discussions`, `--no-follow-renames`, `--merged`, `-n` |
|
||||
| `trace` | `<PATH>` | file->MR->issue->discussion chains | `-p`, `--discussions`, `--no-follow-renames`, `-n` |
|
||||
| `who --path <PATH>` | `<PATH>` via flag | experts for file area | `-p`, `--since`, `-n` |
|
||||
| `who --overlap <PATH>` | `<PATH>` via flag | users touching same files | `-p`, `--since`, `-n` |
|
||||
|
||||
**Problem:** `trace` is a superset of `file-history` -- it follows the same MR chain but additionally links to closing issues and discussions. They share 4 of 5 filter flags. A user who wants "what happened to this file?" has to choose between two commands that sound nearly identical.
|
||||
|
||||
### Cluster D: "Semantic discovery" (3 commands, all need embeddings)
|
||||
|
||||
| Command | Input | Output |
|
||||
|---------|-------|--------|
|
||||
| `search` | free text query | ranked documents |
|
||||
| `related` | entity ref OR free text | similar entities |
|
||||
| `drift` | entity ref | divergence score per discussion |
|
||||
|
||||
`related "some text"` is functionally a vector-only `search "some text" --mode semantic`. The difference is that `related` can also seed from an entity (issues 42), while `search` only accepts text.
|
||||
|
||||
`drift` is specialized enough to stand alone, but it's only used on issues and has a single non-project flag (`--threshold`).
|
||||
|
||||
### Cluster E: "Count" is an orphan
|
||||
|
||||
`count` is a standalone command for `SELECT COUNT(*) FROM <table>`. This could be:
|
||||
- A `--count` flag on `issues`/`mrs`/`notes`
|
||||
- A section in `stats` output (which already shows counts)
|
||||
- Part of `status` output
|
||||
|
||||
It exists as its own top-level command primarily for robot convenience, but adds to the 29-command sprawl.
|
||||
|
||||
---
|
||||
|
||||
## 3. Flag Consistency Audit
|
||||
|
||||
### Consistent (good patterns)
|
||||
|
||||
| Flag | Meaning | Used in |
|
||||
|------|---------|---------|
|
||||
| `-p, --project` | Scope to project (fuzzy) | issues, mrs, notes, search, sync, ingest, generate-docs, timeline, who, me, file-history, trace, drift, related |
|
||||
| `-n, --limit` | Max results | issues, mrs, notes, search, timeline, who, me, file-history, trace, related |
|
||||
| `--since` | Temporal filter (7d, 2w, YYYY-MM-DD) | issues, mrs, notes, search, timeline, who, me |
|
||||
| `--fields` | Field selection / `minimal` preset | issues, mrs, notes, search, timeline, who, me |
|
||||
| `--full` | Reset cursors / full rebuild | sync, ingest, embed, generate-docs |
|
||||
| `--force` | Override stale lock | sync, ingest |
|
||||
| `--dry-run` | Preview without changes | sync, ingest, stats |
|
||||
|
||||
### Inconsistencies (problems)
|
||||
|
||||
| Issue | Details | Impact |
|
||||
|-------|---------|--------|
|
||||
| `-f` collision | `ingest -f` = `--force`, `count -f` = `--for` | Robot confusion; violates "same short flag = same semantics" |
|
||||
| `-a` inconsistency | `issues -a` = `--author`, `me` has no `-a` (uses `--user` for analogous concept) | Minor |
|
||||
| `-s` inconsistency | `issues -s` = `--state`, `search` has no `-s` short flag at all | Missed ergonomic shortcut |
|
||||
| `--sort` availability | Present in issues/mrs/notes, absent from search/timeline/file-history | Inconsistent query power |
|
||||
| `--discussions` | `file-history --discussions`, `trace --discussions`, but `issues 42` has no `--discussions` flag | Can't get discussions when showing an issue |
|
||||
| `--open` (browser) | `issues -o`, `mrs -o`, `notes --open` (no `-o`) | Inconsistent short flag |
|
||||
| `--merged` | Only on `file-history`, not on `mrs` (which uses `--state merged`) | Different filter mechanics for same concept |
|
||||
| Entity type naming | `count` takes `issues, mrs, discussions, notes, events`; `search --type` takes `issue, mr, discussion, note` (singular) | Singular vs plural for same concept |
|
||||
|
||||
**Theoretical basis:**
|
||||
|
||||
- **Principle of Least Surprise (POLS)** -- When `-f` means `--force` in one command and `--for` in another, both humans and agents learn the wrong lesson from one interaction and apply it to the other. CLI design guides (GNU standards, POSIX conventions, clig.dev) are unanimous: short flags should have consistent semantics across all subcommands.
|
||||
|
||||
- **Singular/plural inconsistency** (`issues` vs `issue` as entity type values) is particularly harmful for LLM agents, which use pattern matching on prior successful invocations. If `lore count issues` works, the agent will try `lore search --type issues` -- and get a parse error.
|
||||
|
||||
---
|
||||
|
||||
## 4. Robot Ergonomics Assessment
|
||||
|
||||
### Strengths (well above average for a CLI)
|
||||
|
||||
| Feature | Rating | Notes |
|
||||
|---------|--------|-------|
|
||||
| Structured output | Excellent | Consistent `{ok, data, meta}` envelope |
|
||||
| Auto-detection | Excellent | Non-TTY -> robot mode, `LORE_ROBOT` env var |
|
||||
| Error output | Excellent | Structured JSON to stderr with `actions` array for recovery |
|
||||
| Exit codes | Excellent | 20 distinct, well-documented codes |
|
||||
| Self-discovery | Excellent | `robot-docs` manifest, `--brief` for token savings |
|
||||
| Typo tolerance | Excellent | Autocorrect with confidence scores + structured warnings |
|
||||
| Field selection | Good | `--fields minimal` saves ~60% tokens |
|
||||
| No-args behavior | Good | Robot mode auto-outputs robot-docs |
|
||||
|
||||
### Weaknesses
|
||||
|
||||
| Issue | Severity | Recommendation |
|
||||
|-------|----------|----------------|
|
||||
| 29 commands in robot-docs manifest | High | Agents spend tokens evaluating which command to use. Grouping would reduce decision space. |
|
||||
| `status`/`stats`/`stat` near-homonyms | High | LLMs are particularly susceptible to surface-level lexical confusion. `stat` is an alias for `stats` while `status` is a different command -- this guarantees agent errors. |
|
||||
| Singular vs plural entity types | Medium | `count issues` works but `search --type issues` fails. Agents learn from one and apply to the other. |
|
||||
| Overlapping file commands | Medium | Agent must decide between `trace`, `file-history`, and `who --path`. The decision tree isn't obvious from names alone. |
|
||||
| `count` as separate command | Low | Could be a flag; standalone command inflates the decision space |
|
||||
|
||||
---
|
||||
|
||||
## 5. Human Ergonomics Assessment
|
||||
|
||||
### Strengths
|
||||
|
||||
| Feature | Rating | Notes |
|
||||
|---------|--------|-------|
|
||||
| Help text quality | Excellent | Every command has examples, help headings organize flags |
|
||||
| Short flags | Good | `-p`, `-n`, `-s`, `-a`, `-J` cover 80% of common use |
|
||||
| Alias coverage | Good | `issue`/`issues`, `mr`/`mrs`, `st`/`status`, `find`/`search` |
|
||||
| Subcommand inference | Good | `lore issu` -> `issues` via clap infer |
|
||||
| Color/icon system | Good | Auto, with overrides |
|
||||
|
||||
### Weaknesses
|
||||
|
||||
| Issue | Severity | Recommendation |
|
||||
|-------|----------|----------------|
|
||||
| 29 commands in flat help | High | Doesn't fit one terminal screen. No grouping -> overwhelming |
|
||||
| `status` vs `stats` naming | High | Humans will type wrong one repeatedly |
|
||||
| `health` vs `doctor` distinction | Medium | "Which one do I run?" -- unclear from names |
|
||||
| `who` 5-mode overload | Medium | Help text is long; mode exclusions are complex |
|
||||
| Pipeline stages as top-level | Low | `ingest`/`generate-docs`/`embed` rarely used directly but clutter help |
|
||||
| `generate-docs` is 14 chars | Low | Longest command name; `gen-docs` or `gendocs` would help |
|
||||
|
||||
---
|
||||
|
||||
## 6. Proposals (Ranked by Impact x Feasibility)
|
||||
|
||||
### P1: Help Grouping (HIGH impact, LOW effort)
|
||||
|
||||
**Problem:** 29 flat commands -> information overload.
|
||||
|
||||
**Fix:** Use clap's `help_heading` on subcommands to group them:
|
||||
|
||||
```
|
||||
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]
|
||||
count Count entities in local database
|
||||
|
||||
Intelligence:
|
||||
timeline Chronological timeline of events
|
||||
who People intelligence: experts, workload, overlap
|
||||
me Personal work dashboard
|
||||
|
||||
File Analysis:
|
||||
trace Trace why code was introduced
|
||||
file-history Show MRs that touched a file
|
||||
related Find semantically related entities
|
||||
drift Detect discussion divergence
|
||||
|
||||
Data Pipeline:
|
||||
sync Run full sync pipeline
|
||||
ingest Ingest data from GitLab
|
||||
generate-docs Generate searchable documents
|
||||
embed Generate vector embeddings
|
||||
|
||||
System:
|
||||
init Initialize configuration and database
|
||||
status Show sync state [aliases: st]
|
||||
health Quick health check
|
||||
doctor Check environment health
|
||||
stats Document and index statistics [aliases: stat]
|
||||
auth Verify GitLab authentication
|
||||
token Manage stored GitLab token
|
||||
migrate Run pending database migrations
|
||||
cron Manage automatic syncing
|
||||
completions Generate shell completions
|
||||
robot-docs Agent self-discovery manifest
|
||||
version Show version information
|
||||
```
|
||||
|
||||
**Effort:** ~20 lines of `#[command(help_heading = "...")]` annotations. No behavior changes.
|
||||
|
||||
### P2: Resolve `status`/`stats` Confusion (HIGH impact, LOW effort)
|
||||
|
||||
**Option A (recommended):** Rename `stats` -> `index`.
|
||||
- `lore status` = when did I last sync? (pipeline state)
|
||||
- `lore index` = how big is my index? (data inventory)
|
||||
- The alias `stat` goes away (it was causing confusion anyway)
|
||||
|
||||
**Option B:** Rename `status` -> `sync-state` and `stats` -> `db-stats`. More descriptive but longer.
|
||||
|
||||
**Option C:** Merge both under `check` (see P4).
|
||||
|
||||
### P3: Fix Singular/Plural Entity Type Inconsistency (MEDIUM impact, TRIVIAL effort)
|
||||
|
||||
Accept both singular and plural forms everywhere:
|
||||
- `count` already takes `issues` (plural) -- also accept `issue`
|
||||
- `search --type` already takes `issue` (singular) -- also accept `issues`
|
||||
- `drift` takes `issues` -- also accept `issue`
|
||||
|
||||
This is a ~10 line change in the value parsers and eliminates an entire class of agent errors.
|
||||
|
||||
### P4: Merge `health` + `doctor` (MEDIUM impact, LOW effort)
|
||||
|
||||
`health` is a fast subset of `doctor`. Merge:
|
||||
- `lore doctor` = full diagnostic (current behavior)
|
||||
- `lore doctor --quick` = fast pre-flight, exit-code-only (current `health`)
|
||||
- Drop `health` as a separate command, add a hidden alias for backward compat
|
||||
|
||||
### P5: Fix `-f` Short Flag Collision (MEDIUM impact, TRIVIAL effort)
|
||||
|
||||
Change `count`'s `-f, --for` to just `--for` (no short flag). `-f` should mean `--force` project-wide, or nowhere.
|
||||
|
||||
### P6: Consolidate `trace` + `file-history` (MEDIUM impact, MEDIUM effort)
|
||||
|
||||
`trace` already does everything `file-history` does plus more. Options:
|
||||
|
||||
**Option A:** Make `file-history` an alias for `trace --flat` (shows MR list without issue/discussion linking).
|
||||
|
||||
**Option B:** Add `--mrs-only` to `trace` that produces `file-history` output. Deprecate `file-history` with a hidden alias.
|
||||
|
||||
Either way, one fewer top-level command and no lost functionality.
|
||||
|
||||
### P7: Hide Pipeline Sub-stages (LOW impact, TRIVIAL effort)
|
||||
|
||||
Move `ingest`, `generate-docs`, `embed` to `#[command(hide = true)]`. They remain usable but don't clutter `--help`. Direct users to `sync` with stage-skip flags.
|
||||
|
||||
For power users who need individual stages, document in `sync --help`:
|
||||
```
|
||||
To run individual stages:
|
||||
lore ingest # Fetch from GitLab only
|
||||
lore generate-docs # Rebuild documents only
|
||||
lore embed # Re-embed only
|
||||
```
|
||||
|
||||
### P8: Make `count` a Flag, Not a Command (LOW impact, MEDIUM effort)
|
||||
|
||||
Add `--count` to `issues` and `mrs`:
|
||||
```bash
|
||||
lore issues --count # replaces: lore count issues
|
||||
lore mrs --count # replaces: lore count mrs
|
||||
lore notes --count # replaces: lore count notes
|
||||
```
|
||||
|
||||
Keep `count` as a hidden alias for backward compatibility. Removes one top-level command.
|
||||
|
||||
### P9: Consistent `--open` Short Flag (LOW impact, TRIVIAL effort)
|
||||
|
||||
`notes --open` lacks the `-o` shorthand that `issues` and `mrs` have. Add it.
|
||||
|
||||
### P10: Add `--sort` to `search` (LOW impact, LOW effort)
|
||||
|
||||
`search` returns ranked results but offers no `--sort` override. Adding `--sort=score,created,updated` would bring it in line with `issues`/`mrs`/`notes`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Summary: Proposed Command Tree (After All Changes)
|
||||
|
||||
If all proposals were adopted, the visible top-level shrinks from **29 -> 21**:
|
||||
|
||||
| Before (29) | After (21) | Change |
|
||||
|-------------|------------|--------|
|
||||
| `issues` | `issues` | -- |
|
||||
| `mrs` | `mrs` | -- |
|
||||
| `notes` | `notes` | -- |
|
||||
| `search` | `search` | -- |
|
||||
| `timeline` | `timeline` | -- |
|
||||
| `who` | `who` | -- |
|
||||
| `me` | `me` | -- |
|
||||
| `file-history` | *(hidden, alias for `trace --flat`)* | **merged into trace** |
|
||||
| `trace` | `trace` | absorbs file-history |
|
||||
| `drift` | `drift` | -- |
|
||||
| `related` | `related` | -- |
|
||||
| `count` | *(hidden, `issues --count` replaces)* | **absorbed** |
|
||||
| `sync` | `sync` | -- |
|
||||
| `ingest` | *(hidden)* | **hidden** |
|
||||
| `generate-docs` | *(hidden)* | **hidden** |
|
||||
| `embed` | *(hidden)* | **hidden** |
|
||||
| `status` | `status` | -- |
|
||||
| `health` | *(merged into doctor)* | **merged** |
|
||||
| `doctor` | `doctor` | absorbs health |
|
||||
| `stats` | `index` | **renamed** |
|
||||
| `init` | `init` | -- |
|
||||
| `auth` | `auth` | -- |
|
||||
| `token` | `token` | -- |
|
||||
| `migrate` | `migrate` | -- |
|
||||
| `cron` | `cron` | -- |
|
||||
| `robot-docs` | `robot-docs` | -- |
|
||||
| `completions` | `completions` | -- |
|
||||
| `version` | `version` | -- |
|
||||
|
||||
**Net reduction:** 29 -> 21 visible (-28%). The hidden commands remain fully functional and documented in `robot-docs` for agents that already use them.
|
||||
|
||||
**Theoretical basis:**
|
||||
|
||||
- **Miller's Law** -- Humans can hold 7+/-2 items in working memory. 29 commands far exceeds this. Even with help grouping (P1), the sheer count creates decision fatigue. The literature on CLI design (Heroku's "12-Factor CLI", clig.dev's "Command Line Interface Guidelines") recommends 10-15 top-level commands maximum, with grouping or nesting for anything beyond.
|
||||
|
||||
- **For LLM agents specifically:** Research on tool-use with large tool sets (Schick et al. 2023, Qin et al. 2023) shows that agent accuracy degrades as the tool count increases, roughly following an inverse log curve. Reducing from 29 to 21 commands in the robot-docs manifest would measurably improve agent command selection accuracy.
|
||||
|
||||
- **Backward compatibility is free:** Since AGENTS.md says "we don't care about backward compatibility," hidden aliases cost nothing and prevent breakage for agents with cached robot-docs.
|
||||
|
||||
---
|
||||
|
||||
## 8. Priority Matrix
|
||||
|
||||
| Proposal | Impact | Effort | Risk | Recommended Order |
|
||||
|----------|--------|--------|------|-------------------|
|
||||
| P1: Help grouping | High | Trivial | None | **Do first** |
|
||||
| P3: Singular/plural fix | Medium | Trivial | None | **Do first** |
|
||||
| P5: Fix `-f` collision | Medium | Trivial | None | **Do first** |
|
||||
| P9: `notes -o` shorthand | Low | Trivial | None | **Do first** |
|
||||
| P2: Rename `stats`->`index` | High | Low | Alias needed | **Do second** |
|
||||
| P4: Merge health->doctor | Medium | Low | Alias needed | **Do second** |
|
||||
| P7: Hide pipeline stages | Low | Trivial | Needs docs update | **Do second** |
|
||||
| P6: Merge file-history->trace | Medium | Medium | Flag design | **Plan carefully** |
|
||||
| P8: count -> --count flag | Low | Medium | Compat shim | **Plan carefully** |
|
||||
| P10: `--sort` on search | Low | Low | None | **When convenient** |
|
||||
|
||||
The "do first" tier is 4 changes that could ship in a single commit with zero risk and immediate ergonomic improvement for both humans and agents.
|
||||
966
command-restructure/IMPLEMENTATION_PLAN.md
Normal file
966
command-restructure/IMPLEMENTATION_PLAN.md
Normal file
@@ -0,0 +1,966 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user