#[derive(Serialize)] struct RobotDocsOutput { ok: bool, data: RobotDocsData, } #[derive(Serialize)] struct RobotDocsData { name: String, version: String, description: String, activation: RobotDocsActivation, quick_start: serde_json::Value, commands: serde_json::Value, /// Deprecated command aliases (old -> new) aliases: serde_json::Value, /// Pre-clap error tolerance: what the CLI auto-corrects error_tolerance: serde_json::Value, exit_codes: serde_json::Value, /// Error codes emitted by clap parse failures clap_error_codes: serde_json::Value, error_format: String, workflows: serde_json::Value, config_notes: serde_json::Value, } #[derive(Serialize)] struct RobotDocsActivation { flags: Vec, env: String, auto: String, } fn handle_robot_docs(robot_mode: bool, brief: bool) -> Result<(), Box> { let version = env!("CARGO_PKG_VERSION").to_string(); let commands = serde_json::json!({ "init": { "description": "Initialize configuration and database", "flags": ["--force", "--non-interactive", "--gitlab-url ", "--token-env-var ", "--projects ", "--default-project "], "robot_flags": ["--gitlab-url", "--token-env-var", "--projects", "--default-project"], "example": "lore --robot init --gitlab-url https://gitlab.com --token-env-var GITLAB_TOKEN --projects group/project,other/repo --default-project group/project", "response_schema": { "ok": "bool", "data": {"config_path": "string", "data_dir": "string", "user": {"username": "string", "name": "string"}, "projects": "[{path:string, name:string}]", "default_project": "string?"}, "meta": {"elapsed_ms": "int"} } }, "health": { "description": "Quick pre-flight check: config, database, schema version", "flags": [], "example": "lore --robot health", "response_schema": { "ok": "bool", "data": {"healthy": "bool", "config_found": "bool", "db_found": "bool", "schema_current": "bool", "schema_version": "int"}, "meta": {"elapsed_ms": "int"} } }, "auth": { "description": "Verify GitLab authentication", "flags": [], "example": "lore --robot auth", "response_schema": { "ok": "bool", "data": {"authenticated": "bool", "username": "string", "name": "string", "gitlab_url": "string"}, "meta": {"elapsed_ms": "int"} } }, "doctor": { "description": "Full environment health check (config, auth, DB, Ollama)", "flags": [], "example": "lore --robot doctor", "response_schema": { "ok": "bool", "data": {"success": "bool", "checks": "{config:object, auth:object, database:object, ollama:object}"}, "meta": {"elapsed_ms": "int"} } }, "ingest": { "description": "Sync data from GitLab", "flags": ["--project ", "--force", "--no-force", "--full", "--no-full", "--dry-run", "--no-dry-run", ""], "example": "lore --robot ingest issues --project group/repo", "response_schema": { "ok": "bool", "data": {"resource_type": "string", "projects_synced": "int", "issues_fetched?": "int", "mrs_fetched?": "int", "upserted": "int", "labels_created": "int", "discussions_fetched": "int", "notes_upserted": "int"}, "meta": {"elapsed_ms": "int"} } }, "sync": { "description": "Full sync pipeline: ingest -> generate-docs -> embed. Supports surgical per-IID mode.", "flags": ["--full", "--no-full", "--force", "--no-force", "--no-embed", "--no-docs", "--no-events", "--no-file-changes", "--no-status", "--dry-run", "--no-dry-run", "-t/--timings", "--lock", "--issue ", "--mr ", "-p/--project ", "--preflight-only"], "example": "lore --robot sync", "surgical_mode": { "description": "Sync specific issues or MRs by IID. Runs a scoped pipeline: preflight -> TOCTOU check -> ingest -> dependents -> docs -> embed.", "flags": ["--issue (repeatable)", "--mr (repeatable)", "-p/--project (required)", "--preflight-only"], "examples": [ "lore --robot sync --issue 7 -p group/project", "lore --robot sync --issue 7 --issue 42 --mr 10 -p group/project", "lore --robot sync --issue 7 -p group/project --preflight-only" ], "constraints": ["--issue/--mr requires -p/--project (or defaultProject in config)", "--full and --issue/--mr are incompatible", "--preflight-only requires --issue or --mr", "Max 100 total targets"], "entity_result_outcomes": ["synced", "skipped_stale", "not_found", "preflight_failed", "error"] }, "response_schema": { "normal": { "ok": "bool", "data": {"issues_updated": "int", "mrs_updated": "int", "documents_regenerated": "int", "documents_embedded": "int", "resource_events_synced": "int", "resource_events_failed": "int"}, "meta": {"elapsed_ms": "int", "stages?": "[{name:string, elapsed_ms:int, items_processed:int}]"} }, "surgical": { "ok": "bool", "data": {"surgical_mode": "true", "surgical_iids": "{issues:[int], merge_requests:[int]}", "entity_results": "[{entity_type:string, iid:int, outcome:string, error?:string, toctou_reason?:string}]", "preflight_only?": "bool", "issues_updated": "int", "mrs_updated": "int", "documents_regenerated": "int", "documents_embedded": "int", "discussions_fetched": "int"}, "meta": {"elapsed_ms": "int"} } } }, "issues": { "description": "List issues, or view detail with ", "flags": ["", "-n/--limit", "--fields ", "-s/--state", "--status ", "-p/--project", "-a/--author", "-A/--assignee", "-l/--label", "-m/--milestone", "--since", "--due-before", "--has-due", "--no-has-due", "--sort", "--asc", "--no-asc", "-o/--open", "--no-open"], "example": "lore --robot issues --state opened --limit 10", "notes": { "status_filter": "--status filters by work item status NAME (case-insensitive). Valid values are in meta.available_statuses of any issues list response.", "status_name": "status_name is the board column label (e.g. 'In review', 'Blocked'). This is the canonical status identifier for filtering." }, "response_schema": { "list": { "ok": "bool", "data": {"issues": "[{iid:int, title:string, state:string, author_username:string, labels:[string], assignees:[string], discussion_count:int, unresolved_count:int, created_at_iso:string, updated_at_iso:string, web_url:string?, project_path:string, status_name:string?}]", "total_count": "int", "showing": "int"}, "meta": {"elapsed_ms": "int", "available_statuses": "[string] — all distinct status names in the database, for use with --status filter"} }, "detail": { "ok": "bool", "data": "IssueDetail (full entity with description, discussions, notes, events)", "meta": {"elapsed_ms": "int"} } }, "example_output": {"list": {"ok":true,"data":{"issues":[{"iid":3864,"title":"Switch Health Card","state":"opened","status_name":"In progress","labels":["customer:BNSF"],"assignees":["teernisse"],"discussion_count":12,"updated_at_iso":"2026-02-12T..."}],"total_count":1,"showing":1},"meta":{"elapsed_ms":42}}}, "fields_presets": {"minimal": ["iid", "title", "state", "updated_at_iso"]} }, "mrs": { "description": "List merge requests, or view detail with ", "flags": ["", "-n/--limit", "--fields ", "-s/--state", "-p/--project", "-a/--author", "-A/--assignee", "-r/--reviewer", "-l/--label", "--since", "-d/--draft", "-D/--no-draft", "--target", "--source", "--sort", "--asc", "--no-asc", "-o/--open", "--no-open"], "example": "lore --robot mrs --state opened", "response_schema": { "list": { "ok": "bool", "data": {"mrs": "[{iid:int, title:string, state:string, author_username:string, labels:[string], draft:bool, target_branch:string, source_branch:string, discussion_count:int, unresolved_count:int, created_at_iso:string, updated_at_iso:string, web_url:string?, project_path:string, reviewers:[string]}]", "total_count": "int", "showing": "int"}, "meta": {"elapsed_ms": "int"} }, "detail": { "ok": "bool", "data": "MrDetail (full entity with description, discussions, notes, events)", "meta": {"elapsed_ms": "int"} } }, "example_output": {"list": {"ok":true,"data":{"mrs":[{"iid":200,"title":"Add throw time chart","state":"opened","draft":false,"author_username":"teernisse","target_branch":"main","source_branch":"feat/throw-time","reviewers":["cseiber"],"discussion_count":5,"updated_at_iso":"2026-02-11T..."}],"total_count":1,"showing":1},"meta":{"elapsed_ms":38}}}, "fields_presets": {"minimal": ["iid", "title", "state", "updated_at_iso"]} }, "search": { "description": "Search indexed documents (lexical, hybrid, semantic)", "flags": ["", "--mode", "--type", "--author", "-p/--project", "--label", "--path", "--since", "--updated-since", "-n/--limit", "--fields ", "--explain", "--no-explain", "--fts-mode"], "example": "lore --robot search 'authentication bug' --mode hybrid --limit 10", "response_schema": { "ok": "bool", "data": {"results": "[{document_id:int, source_type:string, title:string, snippet:string, score:float, url:string?, author:string?, created_at:string?, updated_at:string?, project_path:string, labels:[string], paths:[string]}]", "total_results": "int", "query": "string", "mode": "string", "warnings": "[string]"}, "meta": {"elapsed_ms": "int"} }, "example_output": {"ok":true,"data":{"query":"throw time","mode":"hybrid","total_results":3,"results":[{"document_id":42,"source_type":"issue","title":"Switch Health Card","score":0.92,"snippet":"...throw time data from BNSF...","project_path":"vs/typescript-code"}],"warnings":[]},"meta":{"elapsed_ms":85}}, "fields_presets": {"minimal": ["document_id", "title", "source_type", "score"]} }, "count": { "description": "Count entities in local database", "flags": ["", "-f/--for "], "example": "lore --robot count issues", "response_schema": { "ok": "bool", "data": {"entity": "string", "count": "int", "system_excluded?": "int", "breakdown?": {"opened": "int", "closed": "int", "merged?": "int", "locked?": "int"}}, "meta": {"elapsed_ms": "int"} } }, "stats": { "description": "Show document and index statistics", "flags": ["--check", "--no-check", "--repair", "--dry-run", "--no-dry-run"], "example": "lore --robot stats", "response_schema": { "ok": "bool", "data": {"total_documents": "int", "indexed_documents": "int", "embedded_documents": "int", "stale_documents": "int", "integrity?": "object"}, "meta": {"elapsed_ms": "int"} } }, "status": { "description": "Show sync state (cursors, last sync times)", "flags": [], "example": "lore --robot status", "response_schema": { "ok": "bool", "data": {"projects": "[{path:string, issues_cursor:string?, mrs_cursor:string?, last_sync:string?}]"}, "meta": {"elapsed_ms": "int"} } }, "generate-docs": { "description": "Generate searchable documents from ingested data", "flags": ["--full", "-p/--project "], "example": "lore --robot generate-docs --full", "response_schema": { "ok": "bool", "data": {"generated": "int", "updated": "int", "unchanged": "int", "deleted": "int"}, "meta": {"elapsed_ms": "int"} } }, "embed": { "description": "Generate vector embeddings for documents via Ollama", "flags": ["--full", "--no-full", "--retry-failed", "--no-retry-failed"], "example": "lore --robot embed", "response_schema": { "ok": "bool", "data": {"embedded": "int", "skipped": "int", "failed": "int", "total_chunks": "int"}, "meta": {"elapsed_ms": "int"} } }, "migrate": { "description": "Run pending database migrations", "flags": [], "example": "lore --robot migrate", "response_schema": { "ok": "bool", "data": {"before_version": "int", "after_version": "int", "migrated": "bool"}, "meta": {"elapsed_ms": "int"} } }, "version": { "description": "Show version information", "flags": [], "example": "lore --robot version", "response_schema": { "ok": "bool", "data": {"version": "string", "git_hash?": "string"}, "meta": {"elapsed_ms": "int"} } }, "completions": { "description": "Generate shell completions", "flags": [""], "example": "lore completions bash > ~/.local/share/bash-completion/completions/lore" }, "timeline": { "description": "Chronological timeline of events matching a keyword query or entity reference", "flags": ["", "-p/--project", "--since ", "--depth ", "--no-mentions", "-n/--limit", "--fields ", "--max-seeds", "--max-entities", "--max-evidence"], "query_syntax": { "search": "Any text -> hybrid search seeding (FTS5 + vector)", "entity_direct": "issue:N, i:N, mr:N, m:N -> direct entity seeding (no search, no Ollama)" }, "example": "lore --robot timeline issue:42", "response_schema": { "ok": "bool", "data": {"entities": "[{type:string, iid:int, title:string, project_path:string}]", "events": "[{timestamp:string, type:string, entity_type:string, entity_iid:int, detail:string}]", "total_events": "int"}, "meta": {"elapsed_ms": "int", "search_mode": "string (hybrid|lexical|direct)"} }, "fields_presets": {"minimal": ["timestamp", "type", "entity_iid", "detail"]} }, "who": { "description": "People intelligence: experts, workload, active discussions, overlap, review patterns", "flags": ["", "--path ", "--active", "--overlap ", "--reviews", "--since ", "-p/--project", "-n/--limit", "--fields ", "--detail", "--no-detail", "--as-of ", "--explain-score", "--include-bots", "--include-closed", "--all-history"], "modes": { "expert": "lore who -- Who knows about this area? (also: --path for root files)", "workload": "lore who -- What is someone working on?", "reviews": "lore who --reviews -- Review pattern analysis", "active": "lore who --active -- Active unresolved discussions", "overlap": "lore who --overlap -- Who else is touching these files?" }, "example": "lore --robot who src/features/auth/", "response_schema": { "ok": "bool", "data": { "mode": "string", "input": {"target": "string|null", "path": "string|null", "project": "string|null", "since": "string|null", "limit": "int"}, "resolved_input": {"mode": "string", "project_id": "int|null", "project_path": "string|null", "since_ms": "int", "since_iso": "string", "since_mode": "string (default|explicit|none)", "limit": "int"}, "...": "mode-specific fields" }, "meta": {"elapsed_ms": "int"} }, "example_output": {"expert": {"ok":true,"data":{"mode":"expert","result":{"experts":[{"username":"teernisse","score":42,"note_count":15,"diff_note_count":8}]}},"meta":{"elapsed_ms":65}}}, "fields_presets": { "expert_minimal": ["username", "score"], "workload_minimal": ["entity_type", "iid", "title", "state"], "active_minimal": ["entity_type", "iid", "title", "participants"] } }, "trace": { "description": "Trace why code was introduced: file -> MR -> issue -> discussion. Follows rename chains by default.", "flags": ["", "-p/--project ", "--discussions", "--no-follow-renames", "-n/--limit "], "example": "lore --robot trace src/main.rs -p group/repo", "response_schema": { "ok": "bool", "data": {"path": "string", "resolved_paths": "[string]", "trace_chains": "[{mr_iid:int, mr_title:string, mr_state:string, mr_author:string, change_type:string, merged_at_iso:string?, updated_at_iso:string, web_url:string?, issues:[{iid:int, title:string, state:string, reference_type:string, web_url:string?}], discussions:[{discussion_id:string, mr_iid:int, author_username:string, body_snippet:string, path:string, created_at_iso:string}]}]"}, "meta": {"tier": "string (api_only)", "line_requested": "int?", "elapsed_ms": "int", "total_chains": "int", "renames_followed": "bool"} } }, "file-history": { "description": "Show MRs that touched a file, with rename chain resolution and optional DiffNote discussions", "flags": ["", "-p/--project ", "--discussions", "--no-follow-renames", "--merged", "-n/--limit "], "example": "lore --robot file-history src/main.rs -p group/repo", "response_schema": { "ok": "bool", "data": {"path": "string", "rename_chain": "[string]?", "merge_requests": "[{iid:int, title:string, state:string, author_username:string, change_type:string, merged_at_iso:string?, updated_at_iso:string, merge_commit_sha:string?, web_url:string?}]", "discussions": "[{discussion_id:string, author_username:string, body_snippet:string, path:string, created_at_iso:string}]?"}, "meta": {"elapsed_ms": "int", "total_mrs": "int", "renames_followed": "bool", "paths_searched": "int"} } }, "drift": { "description": "Detect discussion divergence from original issue intent", "flags": ["", "", "--threshold <0.0-1.0>", "-p/--project "], "example": "lore --robot drift issues 42 --threshold 0.4", "response_schema": { "ok": "bool", "data": {"entity_type": "string", "iid": "int", "title": "string", "threshold": "float", "divergent_discussions": "[{discussion_id:string, similarity:float, snippet:string}]"}, "meta": {"elapsed_ms": "int"} } }, "explain": { "description": "Auto-generate a structured narrative of an issue or MR", "flags": ["", "", "-p/--project ", "--sections ", "--no-timeline", "--max-decisions ", "--since "], "valid_sections": ["entity", "description", "key_decisions", "activity", "open_threads", "related", "timeline"], "example": "lore --robot explain issues 42 --sections key_decisions,activity --since 30d", "response_schema": { "ok": "bool", "data": {"entity": "{type:string, iid:int, title:string, state:string, author:string, assignees:[string], labels:[string], created_at:string, updated_at:string, url:string?, status_name:string?}", "description_excerpt": "string?", "key_decisions": "[{timestamp:string, actor:string, action:string, context_note:string}]?", "activity": "{state_changes:int, label_changes:int, notes:int, first_event:string?, last_event:string?}?", "open_threads": "[{discussion_id:string, started_by:string, started_at:string, note_count:int, last_note_at:string}]?", "related": "{closing_mrs:[{iid:int, title:string, state:string, web_url:string?}], related_issues:[{entity_type:string, iid:int, title:string?, reference_type:string}]}?", "timeline_excerpt": "[{timestamp:string, event_type:string, actor:string?, summary:string}]?"}, "meta": {"elapsed_ms": "int"} } }, "notes": { "description": "List notes from discussions with rich filtering", "flags": ["--limit/-n ", "--author/-a ", "--note-type ", "--contains ", "--for-issue ", "--for-mr ", "-p/--project ", "--since ", "--until ", "--path ", "--resolution ", "--sort ", "--asc", "--include-system", "--note-id ", "--gitlab-note-id ", "--discussion-id ", "--fields ", "--open"], "robot_flags": ["--format json", "--fields minimal"], "example": "lore --robot notes --author jdefting --since 1y --format json --fields minimal", "response_schema": { "ok": "bool", "data": {"notes": "[NoteListRowJson]", "total_count": "int", "showing": "int"}, "meta": {"elapsed_ms": "int"} } }, "cron": { "description": "Manage cron-based automatic syncing (Unix only)", "subcommands": { "install": {"flags": ["--interval "], "default_interval": 8}, "uninstall": {"flags": []}, "status": {"flags": []} }, "example": "lore --robot cron status", "response_schema": { "ok": "bool", "data": {"action": "string (install|uninstall|status)", "installed?": "bool", "interval_minutes?": "int", "entry?": "string", "log_path?": "string", "replaced?": "bool", "was_installed?": "bool", "last_run_iso?": "string"}, "meta": {"elapsed_ms": "int"} } }, "token": { "description": "Manage stored GitLab token", "subcommands": { "set": {"flags": ["--token "], "note": "Reads from stdin if --token omitted in non-interactive mode"}, "show": {"flags": ["--unmask"]} }, "example": "lore --robot token show", "response_schema": { "ok": "bool", "data": {"action": "string (set|show)", "token_masked?": "string", "token?": "string", "valid?": "bool", "username?": "string"}, "meta": {"elapsed_ms": "int"} } }, "me": { "description": "Personal work dashboard: open issues, authored/reviewing MRs, @mentioned-in items, activity feed, and cursor-based since-last-check inbox with computed attention states", "flags": ["--issues", "--mrs", "--mentions", "--activity", "--since ", "-p/--project ", "--all", "--user ", "--fields ", "--reset-cursor"], "example": "lore --robot me", "response_schema": { "ok": "bool", "data": { "username": "string", "since_iso": "string?", "summary": {"project_count": "int", "open_issue_count": "int", "authored_mr_count": "int", "reviewing_mr_count": "int", "mentioned_in_count": "int", "needs_attention_count": "int"}, "since_last_check": "{cursor_iso:string, total_event_count:int, groups:[{entity_type:string, entity_iid:int, entity_title:string, project:string, events:[{timestamp_iso:string, event_type:string, actor:string?, summary:string, body_preview:string?}]}]}?", "open_issues": "[{project:string, iid:int, title:string, state:string, attention_state:string, attention_reason:string, status_name:string?, labels:[string], updated_at_iso:string, web_url:string?}]", "open_mrs_authored": "[{project:string, iid:int, title:string, state:string, attention_state:string, attention_reason:string, draft:bool, detailed_merge_status:string?, author_username:string?, labels:[string], updated_at_iso:string, web_url:string?}]", "reviewing_mrs": "[same as open_mrs_authored]", "mentioned_in": "[{entity_type:string, project:string, iid:int, title:string, state:string, attention_state:string, attention_reason:string, updated_at_iso:string, web_url:string?}]", "activity": "[{timestamp_iso:string, event_type:string, entity_type:string, entity_iid:int, project:string, actor:string?, is_own:bool, summary:string, body_preview:string?}]" }, "meta": {"elapsed_ms": "int", "gitlab_base_url": "string (GitLab instance URL for constructing entity links: {base_url}/{project}/-/issues/{iid})"} }, "fields_presets": { "me_items_minimal": ["iid", "title", "attention_state", "attention_reason", "updated_at_iso"], "me_mentions_minimal": ["entity_type", "iid", "title", "state", "attention_state", "attention_reason", "updated_at_iso"], "me_activity_minimal": ["timestamp_iso", "event_type", "entity_iid", "actor"] }, "notes": { "attention_states": "needs_attention | not_started | awaiting_response | stale | not_ready", "event_types": "note | status_change | label_change | assign | unassign | review_request | milestone_change", "section_flags": "If none of --issues/--mrs/--mentions/--activity specified, all sections returned", "since_default": "1d for activity feed", "issue_filter": "Only In Progress / In Review status issues shown", "since_last_check": "Cursor-based inbox showing events since last run. Null on first run (no cursor yet). Groups events by entity (issue/MR). Sources: others' comments on your items, @mentions, assignment/review-request notes. Cursor auto-advances after each run. Use --reset-cursor to clear.", "cursor_persistence": "Stored per user in ~/.local/share/lore/me_cursor_.json. --project filters display only for since-last-check; cursor still advances for all projects for that user.", "url_construction": "Use meta.gitlab_base_url + project + entity_type + iid to build links: {gitlab_base_url}/{project}/-/{issues|merge_requests}/{iid}" } }, "robot-docs": { "description": "This command (agent self-discovery manifest)", "flags": ["--brief"], "example": "lore robot-docs --brief" } }); let quick_start = serde_json::json!({ "glab_equivalents": [ { "glab": "glab issue list", "lore": "lore -J issues -n 50", "note": "Richer: includes labels, status, closing MRs, discussion counts" }, { "glab": "glab issue view 123", "lore": "lore -J issues 123", "note": "Includes full discussions, work-item status, cross-references" }, { "glab": "glab issue list -l bug", "lore": "lore -J issues --label bug", "note": "AND logic for multiple --label flags" }, { "glab": "glab mr list", "lore": "lore -J mrs", "note": "Includes draft status, reviewers, discussion counts" }, { "glab": "glab mr view 456", "lore": "lore -J mrs 456", "note": "Includes discussions, review threads, source/target branches" }, { "glab": "glab mr list -s opened", "lore": "lore -J mrs -s opened", "note": "States: opened, merged, closed, locked, all" }, { "glab": "glab api '/projects/:id/issues'", "lore": "lore -J issues -p project", "note": "Fuzzy project matching (suffix or substring)" } ], "lore_exclusive": [ "search: FTS5 + vector hybrid search across all entities", "who: Expert/workload/reviews analysis per file path or person", "timeline: Chronological event reconstruction across entities", "trace: Code provenance chains (file -> MR -> issue -> discussion)", "file-history: MR history per file with rename resolution", "notes: Rich note listing with author, type, resolution, path, and discussion filters", "stats: Database statistics with document/note/discussion counts", "count: Entity counts with state breakdowns", "embed: Generate vector embeddings for semantic search via Ollama", "cron: Automated sync scheduling (Unix)", "token: Secure token management with masked display", "me: Personal work dashboard with attention states, activity feed, cursor-based since-last-check inbox, and needs-attention triage" ], "read_write_split": "lore = ALL reads (issues, MRs, search, who, timeline, intelligence). glab = ALL writes (create, update, approve, merge, CI/CD)." }); // --brief: strip response_schema and example_output from every command (~60% smaller) let mut commands = commands; if brief { strip_schemas(&mut commands); } let exit_codes = serde_json::json!({ "0": "Success", "1": "Internal error", "2": "Usage error (invalid flags or arguments)", "3": "Config invalid", "4": "Token not set", "5": "GitLab auth failed", "6": "Resource not found", "7": "Rate limited", "8": "Network error", "9": "Database locked", "10": "Database error", "11": "Migration failed", "12": "I/O error", "13": "Transform error", "14": "Ollama unavailable", "15": "Ollama model not found", "16": "Embedding failed", "17": "Not found", "18": "Ambiguous match", "19": "Health check failed", "20": "Config not found", "21": "Embeddings not built" }); let workflows = serde_json::json!({ "first_setup": [ "lore --robot init --gitlab-url https://gitlab.com --token-env-var GITLAB_TOKEN --projects group/project", "lore --robot doctor", "lore --robot sync" ], "daily_sync": [ "lore --robot sync" ], "search": [ "lore --robot search 'query' --mode hybrid" ], "pre_flight": [ "lore --robot health" ], "temporal_intelligence": [ "lore --robot sync", "lore --robot timeline '' --since 30d", "lore --robot timeline '' --depth 2" ], "people_intelligence": [ "lore --robot who src/path/to/feature/", "lore --robot who @username", "lore --robot who @username --reviews", "lore --robot who --active --since 7d", "lore --robot who --overlap src/path/", "lore --robot who --path README.md" ], "surgical_sync": [ "lore --robot sync --issue 7 -p group/project", "lore --robot sync --issue 7 --mr 10 -p group/project", "lore --robot sync --issue 7 -p group/project --preflight-only" ], "personal_dashboard": [ "lore --robot me", "lore --robot me --issues", "lore --robot me --activity --since 7d", "lore --robot me --project group/repo", "lore --robot me --fields minimal", "lore --robot me --reset-cursor" ] }); // Phase 3: Deprecated command aliases let aliases = serde_json::json!({ "deprecated_commands": { "list issues": "issues", "list mrs": "mrs", "show issue ": "issues ", "show mr ": "mrs ", "auth-test": "auth", "sync-status": "status" }, "command_aliases": { "issue": "issues", "mr": "mrs", "merge-requests": "mrs", "merge-request": "mrs", "mergerequests": "mrs", "mergerequest": "mrs", "generate-docs": "generate-docs", "generatedocs": "generate-docs", "gendocs": "generate-docs", "gen-docs": "generate-docs", "robot-docs": "robot-docs", "robotdocs": "robot-docs" }, "pre_clap_aliases": { "note": "Underscore/no-separator forms auto-corrected before parsing", "merge_requests": "mrs", "merge_request": "mrs", "mergerequests": "mrs", "mergerequest": "mrs", "generate_docs": "generate-docs", "generatedocs": "generate-docs", "gendocs": "generate-docs", "gen-docs": "generate-docs", "robot-docs": "robot-docs", "robotdocs": "robot-docs" }, "prefix_matching": "Enabled via infer_subcommands. Unambiguous prefixes work: 'iss' -> issues, 'time' -> timeline, 'sea' -> search" }); let error_tolerance = serde_json::json!({ "note": "The CLI auto-corrects common mistakes before parsing. Corrections are applied silently with a teaching note on stderr.", "auto_corrections": [ {"type": "single_dash_long_flag", "example": "-robot -> --robot", "mode": "all"}, {"type": "case_normalization", "example": "--Robot -> --robot, --State -> --state", "mode": "all"}, {"type": "flag_prefix", "example": "--proj -> --project (when unambiguous)", "mode": "all"}, {"type": "fuzzy_flag", "example": "--projct -> --project", "mode": "all (threshold 0.9 in robot, 0.8 in human)"}, {"type": "subcommand_alias", "example": "merge_requests -> mrs, robotdocs -> robot-docs", "mode": "all"}, {"type": "subcommand_fuzzy", "example": "issuess -> issues, timline -> timeline, serach -> search", "mode": "all (threshold 0.85)"}, {"type": "flag_as_subcommand", "example": "--robot-docs -> robot-docs, --generate-docs -> generate-docs", "mode": "all"}, {"type": "value_normalization", "example": "--state Opened -> --state opened", "mode": "all"}, {"type": "value_fuzzy", "example": "--state opend -> --state opened", "mode": "all"}, {"type": "prefix_matching", "example": "lore iss -> lore issues, lore time -> lore timeline", "mode": "all (via clap infer_subcommands)"} ], "teaching_notes": "Auto-corrections emit a JSON warning on stderr: {\"warning\":{\"type\":\"ARG_CORRECTED\",\"corrections\":[...],\"teaching\":[...]}}" }); // Phase 3: Clap error codes (emitted by handle_clap_error) let clap_error_codes = serde_json::json!({ "UNKNOWN_COMMAND": "Unrecognized subcommand (includes fuzzy suggestion)", "UNKNOWN_FLAG": "Unrecognized command-line flag", "MISSING_REQUIRED": "Required argument not provided", "INVALID_VALUE": "Invalid value for argument", "TOO_MANY_VALUES": "Too many values provided", "TOO_FEW_VALUES": "Too few values provided", "ARGUMENT_CONFLICT": "Conflicting arguments", "MISSING_COMMAND": "No subcommand provided (in non-robot mode, shows help)", "HELP_REQUESTED": "Help or version flag used", "PARSE_ERROR": "General parse error" }); let config_notes = serde_json::json!({ "defaultProject": { "type": "string?", "description": "Fallback project path used when -p/--project is omitted. Must match a configured project path (exact or suffix). CLI -p always overrides.", "example": "group/project" } }); let output = RobotDocsOutput { ok: true, data: RobotDocsData { name: "lore".to_string(), version, description: "Local GitLab data management with semantic search".to_string(), activation: RobotDocsActivation { flags: vec!["--robot".to_string(), "-J".to_string(), "--json".to_string()], env: "LORE_ROBOT=1".to_string(), auto: "Non-TTY stdout".to_string(), }, quick_start, commands, aliases, error_tolerance, exit_codes, clap_error_codes, error_format: "stderr JSON: {\"error\":{\"code\":\"...\",\"message\":\"...\",\"suggestion\":\"...\",\"actions\":[\"...\"]}}".to_string(), workflows, config_notes, }, }; if robot_mode { println!("{}", serde_json::to_string(&output)?); } else { println!("{}", serde_json::to_string_pretty(&output)?); } Ok(()) } fn handle_who( config_override: Option<&str>, mut args: WhoArgs, robot_mode: bool, ) -> Result<(), Box> { let start = std::time::Instant::now(); let config = Config::load(config_override)?; if args.project.is_none() { args.project = config.default_project.clone(); } let run = run_who(&config, &args)?; let elapsed_ms = start.elapsed().as_millis() as u64; if robot_mode { print_who_json(&run, &args, elapsed_ms); } else { print_who_human(&run.result, run.resolved_input.project_path.as_deref()); } Ok(()) } fn handle_me( config_override: Option<&str>, args: MeArgs, robot_mode: bool, ) -> Result<(), Box> { let config = Config::load(config_override)?; run_me(&config, &args, robot_mode)?; Ok(()) } async fn handle_drift( config_override: Option<&str>, entity_type: &str, iid: i64, threshold: f32, project: Option<&str>, robot_mode: bool, ) -> Result<(), Box> { let start = std::time::Instant::now(); let config = Config::load(config_override)?; let effective_project = config.effective_project(project); let response = run_drift(&config, entity_type, iid, threshold, effective_project).await?; let elapsed_ms = start.elapsed().as_millis() as u64; if robot_mode { print_drift_json(&response, elapsed_ms); } else { print_drift_human(&response); } Ok(()) } async fn handle_related( config_override: Option<&str>, query_or_type: &str, iid: Option, limit: usize, project: Option<&str>, robot_mode: bool, ) -> Result<(), Box> { let start = std::time::Instant::now(); let config = Config::load(config_override)?; let effective_project = config.effective_project(project); let response = run_related(&config, query_or_type, iid, limit, effective_project).await?; let elapsed_ms = start.elapsed().as_millis() as u64; if robot_mode { print_related_json(&response, elapsed_ms); } else { print_related_human(&response); } Ok(()) } #[allow(clippy::too_many_arguments)] async fn handle_list_compat( config_override: Option<&str>, entity: &str, limit: usize, project_filter: Option<&str>, state_filter: Option<&str>, author_filter: Option<&str>, assignee_filter: Option<&str>, label_filter: Option<&[String]>, milestone_filter: Option<&str>, since_filter: Option<&str>, due_before_filter: Option<&str>, has_due_date: bool, sort: &str, order: &str, open_browser: bool, json_output: bool, draft: bool, no_draft: bool, reviewer_filter: Option<&str>, target_branch_filter: Option<&str>, source_branch_filter: Option<&str>, ) -> Result<(), Box> { let start = std::time::Instant::now(); let config = Config::load(config_override)?; let project_filter = config.effective_project(project_filter); let state_normalized = state_filter.map(str::to_lowercase); match entity { "issues" => { let filters = ListFilters { limit, project: project_filter, state: state_normalized.as_deref(), author: author_filter, assignee: assignee_filter, labels: label_filter, milestone: milestone_filter, since: since_filter, due_before: due_before_filter, has_due_date, statuses: &[], sort, order, }; let result = run_list_issues(&config, filters)?; if open_browser { open_issue_in_browser(&result); } else if json_output { print_list_issues_json(&result, start.elapsed().as_millis() as u64, None); } else { print_list_issues(&result); } Ok(()) } "mrs" => { let filters = MrListFilters { limit, project: project_filter, state: state_normalized.as_deref(), author: author_filter, assignee: assignee_filter, reviewer: reviewer_filter, labels: label_filter, since: since_filter, draft, no_draft, target_branch: target_branch_filter, source_branch: source_branch_filter, sort, order, }; let result = run_list_mrs(&config, filters)?; if open_browser { open_mr_in_browser(&result); } else if json_output { print_list_mrs_json(&result, start.elapsed().as_millis() as u64, None); } else { print_list_mrs(&result); } Ok(()) } _ => { eprintln!( "{}", Theme::error().render(&format!("Unknown entity: {entity}")) ); std::process::exit(1); } } }