Skip to main content

gobby_code/
contract.rs

1use gobby_core::cli_contract::{
2    CliContract, CommandContract, FlagContract, PositionalContract, ScopeContract,
3};
4
5pub fn contract() -> CliContract {
6    CliContract {
7        tool: "gcode",
8        // Additive optional flags are backward-compatible and do not bump this version.
9        contract_version: 2,
10        summary: "Fast code index CLI for Gobby.",
11        global_flags: vec![
12            FlagContract::value("--project", "ROOT"),
13            format_flag(),
14            FlagContract::switch("--quiet"),
15            FlagContract::switch("--verbose"),
16            FlagContract::switch("--no-freshness"),
17        ],
18        scope: Some(ScopeContract {
19            flags: vec![FlagContract::value("--project", "ROOT")],
20            default: "detect project from current working directory",
21            identity_keys: vec!["project_id", "project_root"],
22        }),
23        commands: vec![
24            CommandContract {
25                daemon_consumed: true,
26                positionals: vec![],
27                flags: vec![format_flag()],
28                json_output_keys: contract_keys(),
29                ..CommandContract::new("contract", "Emit this CLI contract.")
30            },
31            CommandContract {
32                positionals: vec![],
33                flags: vec![],
34                json_output_keys: vec![],
35                ..CommandContract::new(
36                    "init",
37                    "Initialize project context for the current repository.",
38                )
39            },
40            CommandContract {
41                positionals: vec![],
42                flags: vec![
43                    FlagContract::switch("--standalone").required(),
44                    FlagContract::value("--database-url", "DATABASE_URL"),
45                    FlagContract::switch("--no-services"),
46                    FlagContract::switch("--overwrite-code-index"),
47                    FlagContract::value("--schema", "SCHEMA"),
48                    FlagContract::value("--embedding-provider", "PROVIDER"),
49                    FlagContract::value("--embedding-api-base", "URL"),
50                    FlagContract::value("--embedding-model", "MODEL"),
51                    FlagContract::value("--embedding-query-prefix", "PREFIX"),
52                    FlagContract::value("--embedding-vector-dim", "N"),
53                    FlagContract::value("--embedding-api-key", "KEY"),
54                    FlagContract::value("--falkordb-host", "HOST"),
55                    FlagContract::value("--falkordb-port", "PORT"),
56                    FlagContract::value("--falkordb-password", "PASSWORD"),
57                    FlagContract::value("--qdrant-url", "URL"),
58                ],
59                json_output_keys: vec![],
60                ..CommandContract::new(
61                    "setup",
62                    "Create gcode-owned standalone database objects and local service config.",
63                )
64            },
65            CommandContract {
66                daemon_consumed: true,
67                positionals: vec![PositionalContract {
68                    name: "PATH",
69                    required: false,
70                    repeatable: false,
71                }],
72                flags: vec![
73                    FlagContract::repeatable_value("--files", "FILE"),
74                    FlagContract::switch("--full"),
75                    FlagContract::switch("--require-cpp-semantics"),
76                    FlagContract::switch("--sync-projections"),
77                ],
78                json_output_keys: vec![
79                    "project_id",
80                    "root",
81                    "indexed_files",
82                    "indexed_symbols",
83                    "skipped_files",
84                    "errors",
85                ],
86                ..CommandContract::new(
87                    "index",
88                    "Index a directory or specific files into the code index.",
89                )
90            },
91            CommandContract {
92                positionals: vec![],
93                flags: vec![format_flag()],
94                json_output_keys: vec![],
95                ..CommandContract::new("status", "Show project index status.")
96            },
97            CommandContract {
98                positionals: vec![],
99                flags: vec![FlagContract::switch("--force")],
100                json_output_keys: vec![],
101                ..CommandContract::new("invalidate", "Clear index state and force re-index.")
102            },
103            CommandContract {
104                daemon_consumed: true,
105                positionals: vec![
106                    PositionalContract::required("QUERY"),
107                    PositionalContract {
108                        name: "PATH",
109                        required: false,
110                        repeatable: true,
111                    },
112                ],
113                flags: {
114                    let mut flags = search_flags();
115                    flags.push(token_budget_flag());
116                    flags
117                },
118                json_output_keys: search_keys(),
119                ..CommandContract::new(
120                    "search",
121                    "Hybrid symbol and content search over the code index.",
122                )
123            },
124            CommandContract {
125                daemon_consumed: true,
126                positionals: vec![
127                    PositionalContract::required("QUERY"),
128                    PositionalContract {
129                        name: "PATH",
130                        required: false,
131                        repeatable: true,
132                    },
133                ],
134                flags: {
135                    let mut flags = search_flags();
136                    flags.push(FlagContract::switch("--with-graph"));
137                    flags
138                },
139                json_output_keys: search_keys(),
140                ..CommandContract::new(
141                    "search-symbol",
142                    "Exact-first symbol/name search with deterministic ranking.",
143                )
144            },
145            CommandContract {
146                positionals: vec![
147                    PositionalContract::required("QUERY"),
148                    PositionalContract {
149                        name: "PATH",
150                        required: false,
151                        repeatable: true,
152                    },
153                ],
154                flags: vec![
155                    FlagContract::value("--limit", "N"),
156                    FlagContract::value("--offset", "N"),
157                    FlagContract::value("--language", "LANG"),
158                ],
159                daemon_consumed: true,
160                json_output_keys: search_keys(),
161                ..CommandContract::new(
162                    "search-text",
163                    "Search indexed symbol metadata with BM25 ranking.",
164                )
165            },
166            CommandContract {
167                positionals: vec![
168                    PositionalContract::required("QUERY"),
169                    PositionalContract {
170                        name: "PATH",
171                        required: false,
172                        repeatable: true,
173                    },
174                ],
175                flags: vec![
176                    FlagContract::value("--limit", "N"),
177                    FlagContract::value("--offset", "N"),
178                    FlagContract::value("--language", "LANG"),
179                ],
180                daemon_consumed: true,
181                json_output_keys: search_keys(),
182                ..CommandContract::new(
183                    "search-content",
184                    "Search indexed file content chunks with BM25 ranking.",
185                )
186            },
187            CommandContract {
188                daemon_consumed: true,
189                positionals: vec![
190                    PositionalContract::required("PATTERN"),
191                    PositionalContract {
192                        name: "PATH",
193                        required: false,
194                        repeatable: true,
195                    },
196                ],
197                flags: grep_flags(),
198                json_output_keys: grep_keys(),
199                ..CommandContract::new(
200                    "grep",
201                    "Indexed exact pattern search over code content chunks.",
202                )
203            },
204            CommandContract {
205                daemon_consumed: true,
206                positionals: vec![PositionalContract::required("FILE")],
207                flags: vec![FlagContract::switch("--summarize")],
208                json_output_keys: outline_keys(),
209                ..CommandContract::new("outline", "Show a hierarchical symbol tree for a file.")
210            },
211            CommandContract {
212                daemon_consumed: true,
213                positionals: vec![PositionalContract::required("ID")],
214                flags: vec![format_flag()],
215                json_output_keys: symbol_keys(),
216                ..CommandContract::new("symbol", "Fetch symbol source code by ID.")
217            },
218            CommandContract {
219                positionals: vec![
220                    PositionalContract::required("PATH[:LINE[:COLUMN]]"),
221                    PositionalContract {
222                        name: "LINE",
223                        required: false,
224                        repeatable: false,
225                    },
226                ],
227                daemon_consumed: true,
228                flags: vec![format_flag()],
229                json_output_keys: symbol_at_keys(),
230                ..CommandContract::new("symbol-at", "Fetch symbol source code at a file location.")
231            },
232            CommandContract {
233                daemon_consumed: true,
234                positionals: vec![PositionalContract::repeatable("ID")],
235                flags: vec![format_flag()],
236                json_output_keys: symbol_record_keys(),
237                ..CommandContract::new("symbols", "Batch retrieve symbols by ID.")
238            },
239            CommandContract {
240                positionals: vec![],
241                flags: vec![format_flag()],
242                json_output_keys: vec![],
243                ..CommandContract::new("kinds", "List distinct symbol kinds in the index.")
244            },
245            CommandContract {
246                daemon_consumed: true,
247                positionals: vec![],
248                flags: vec![format_flag()],
249                json_output_keys: tree_keys(),
250                ..CommandContract::new("tree", "Show file tree with symbol counts.")
251            },
252            CommandContract {
253                daemon_consumed: true,
254                positionals: vec![PositionalContract::required("SYMBOL")],
255                flags: graph_read_flags(),
256                json_output_keys: graph_read_keys(),
257                ..CommandContract::new("callers", "Find callers of a symbol UUID or name.")
258            },
259            CommandContract {
260                daemon_consumed: true,
261                positionals: vec![PositionalContract::required("SYMBOL")],
262                flags: {
263                    let mut flags = graph_read_flags();
264                    flags.push(token_budget_flag());
265                    flags
266                },
267                json_output_keys: graph_read_keys(),
268                ..CommandContract::new(
269                    "usages",
270                    "Find incoming call usages of a symbol UUID or name.",
271                )
272            },
273            CommandContract {
274                daemon_consumed: true,
275                positionals: vec![PositionalContract::required("FILE")],
276                flags: vec![format_flag()],
277                json_output_keys: paged_graph_keys(),
278                ..CommandContract::new("imports", "Show import graph for a file.")
279            },
280            CommandContract {
281                daemon_consumed: true,
282                positionals: vec![
283                    PositionalContract::required("SYMBOL_A"),
284                    PositionalContract::required("SYMBOL_B"),
285                ],
286                flags: vec![FlagContract::value("--max-depth", "N"), format_flag()],
287                json_output_keys: graph_path_keys(),
288                ..CommandContract::new(
289                    "path",
290                    "Find the shortest CALLS path from one symbol query to another.",
291                )
292            },
293            CommandContract {
294                daemon_consumed: true,
295                positionals: vec![PositionalContract::required("SYMBOL")],
296                flags: vec![
297                    FlagContract::value("--depth", "N"),
298                    token_budget_flag(),
299                    format_flag(),
300                ],
301                json_output_keys: paged_graph_keys(),
302                ..CommandContract::new(
303                    "blast-radius",
304                    "Show transitive impact analysis for a symbol query.",
305                )
306            },
307            CommandContract {
308                daemon_consumed: true,
309                positionals: vec![],
310                flags: vec![
311                    FlagContract::value("--out", "DIR"),
312                    FlagContract::repeatable_value("--scope", "PATH"),
313                    FlagContract::value("--since", "GIT_REF"),
314                    ai_flag(),
315                    ai_depth_flag(),
316                    FlagContract::value("--ai-aggregate-profile", "PROFILE"),
317                    FlagContract::value("--ai-verify-profile", "PROFILE"),
318                    ai_prose_depth_flag(),
319                    ai_register_flag(),
320                    FlagContract::value("--edge-limit", "N"),
321                    FlagContract::switch("--include-docs"),
322                    FlagContract::switch("--repair-citations"),
323                ],
324                // Two JSON shapes: a generation run-summary, or — under
325                // `--repair-citations` — the no-LLM citation-repair summary
326                // (`pages_scanned`..`citations_unresolved`, frozen by the repair
327                // routine). The declared key set is their union.
328                json_output_keys: vec![
329                    "command",
330                    "project_id",
331                    "project_root",
332                    "out_dir",
333                    "generated_pages",
334                    "changed_paths",
335                    "skipped",
336                    "files",
337                    "modules",
338                    "symbols",
339                    "ai_enabled",
340                    "degraded_pages",
341                    "pages_scanned",
342                    "pages_repaired",
343                    "citations_repaired",
344                    "citations_unresolved",
345                ],
346                ..CommandContract::new(
347                    "codewiki",
348                    "Generate vault-ready hierarchical code documentation.",
349                )
350            },
351            CommandContract {
352                daemon_consumed: true,
353                positionals: vec![],
354                flags: vec![
355                    FlagContract::value("--file", "FILE"),
356                    FlagContract::switch("--allow-missing-indexed-file"),
357                    format_flag(),
358                ],
359                json_output_keys: vec![
360                    "success",
361                    "status",
362                    "project_id",
363                    "file_path",
364                    "reason",
365                    "synced_files",
366                    "synced_symbols",
367                    "skipped_files",
368                    "failed_files",
369                    "relationships_written",
370                    "degraded",
371                    "error",
372                    "summary",
373                ],
374                ..CommandContract::new(
375                    "graph sync-file",
376                    "Sync one indexed file into the code-index graph projection.",
377                )
378            },
379            CommandContract {
380                daemon_consumed: true,
381                positionals: vec![],
382                flags: vec![FlagContract::value("--limit", "N"), format_flag()],
383                json_output_keys: graph_payload_keys(),
384                ..CommandContract::new(
385                    "graph overview",
386                    "Show an overview graph for the current project.",
387                )
388            },
389            CommandContract {
390                daemon_consumed: true,
391                positionals: vec![],
392                flags: vec![FlagContract::value("--file", "FILE"), format_flag()],
393                json_output_keys: graph_payload_keys(),
394                ..CommandContract::new(
395                    "graph file",
396                    "Show graph nodes and links for one indexed file.",
397                )
398            },
399            CommandContract {
400                daemon_consumed: true,
401                positionals: vec![],
402                flags: vec![
403                    FlagContract::value("--symbol-id", "SYMBOL_ID"),
404                    FlagContract::value("--limit", "N"),
405                    format_flag(),
406                ],
407                json_output_keys: graph_payload_keys(),
408                ..CommandContract::new("graph neighbors", "Show graph neighbors for one symbol ID.")
409            },
410            CommandContract {
411                daemon_consumed: true,
412                positionals: vec![],
413                flags: vec![
414                    FlagContract::value("--symbol-id", "SYMBOL_ID"),
415                    FlagContract::value("--file", "FILE"),
416                    FlagContract::value("--depth", "N"),
417                    FlagContract::value("--limit", "N"),
418                    format_flag(),
419                ],
420                json_output_keys: graph_payload_keys(),
421                ..CommandContract::new(
422                    "graph blast-radius",
423                    "Show transitive graph impact for a symbol ID or file path.",
424                )
425            },
426            CommandContract {
427                daemon_consumed: true,
428                positionals: vec![],
429                flags: vec![
430                    FlagContract::value("--project-id", "PROJECT_ID"),
431                    format_flag(),
432                ],
433                json_output_keys: graph_lifecycle_keys(),
434                ..CommandContract::new(
435                    "graph clear",
436                    "Clear the current project's code-index graph projection.",
437                )
438            },
439            CommandContract {
440                daemon_consumed: true,
441                positionals: vec![],
442                flags: vec![format_flag()],
443                json_output_keys: graph_lifecycle_keys(),
444                ..CommandContract::new(
445                    "graph rebuild",
446                    "Rebuild the current project's code-index graph projection from PostgreSQL facts.",
447                )
448            },
449            CommandContract {
450                daemon_consumed: true,
451                positionals: vec![],
452                flags: vec![format_flag()],
453                json_output_keys: graph_cleanup_keys(),
454                ..CommandContract::new(
455                    "graph cleanup-orphans",
456                    "Remove graph projection data for files missing from PostgreSQL.",
457                )
458            },
459            CommandContract {
460                positionals: vec![],
461                flags: vec![FlagContract::value("--top-n", "N"), format_flag()],
462                json_output_keys: graph_report_keys(),
463                ..CommandContract::new("graph report", "Generate a project graph report.")
464            },
465            CommandContract {
466                positionals: vec![],
467                flags: vec![
468                    FlagContract::value("--file", "FILE"),
469                    FlagContract::switch("--allow-missing-indexed-file"),
470                    format_flag(),
471                ],
472                json_output_keys: vector_lifecycle_keys(),
473                ..CommandContract::new(
474                    "vector sync-file",
475                    "Sync one indexed file into the code-symbol vector projection.",
476                )
477            },
478            CommandContract {
479                positionals: vec![],
480                flags: vec![format_flag()],
481                json_output_keys: vector_lifecycle_keys(),
482                ..CommandContract::new(
483                    "vector clear",
484                    "Clear the current project's code-symbol vector projection.",
485                )
486            },
487            CommandContract {
488                positionals: vec![],
489                flags: vec![format_flag()],
490                json_output_keys: vector_lifecycle_keys(),
491                ..CommandContract::new(
492                    "vector rebuild",
493                    "Rebuild the current project's code-symbol vector projection from PostgreSQL facts.",
494                )
495            },
496            CommandContract {
497                daemon_consumed: true,
498                positionals: vec![],
499                flags: vec![format_flag()],
500                json_output_keys: vector_cleanup_keys(),
501                ..CommandContract::new(
502                    "vector cleanup-orphans",
503                    "Remove Qdrant code-symbol vectors for files missing from PostgreSQL.",
504                )
505            },
506            CommandContract {
507                positionals: vec![],
508                flags: vec![],
509                json_output_keys: embeddings_doctor_keys(),
510                ..CommandContract::new(
511                    "embeddings doctor",
512                    "Emit embedding configuration doctor JSON.",
513                )
514            },
515            CommandContract {
516                positionals: vec![],
517                flags: vec![format_flag()],
518                json_output_keys: vec![],
519                ..CommandContract::new("repo-outline", "Show directory-grouped project stats.")
520            },
521            CommandContract {
522                positionals: vec![],
523                flags: vec![format_flag()],
524                json_output_keys: vec![],
525                ..CommandContract::new("projects", "List indexed projects.")
526            },
527            CommandContract {
528                positionals: vec![],
529                flags: vec![FlagContract::switch("--force")],
530                json_output_keys: vec![],
531                ..CommandContract::new(
532                    "prune",
533                    "Remove stale project records and reconcile projections across indexed projects.",
534                )
535            },
536        ],
537        error_codes: vec![
538            "invalid_input",
539            "missing_project",
540            "backend_unavailable",
541            "index_unavailable",
542            "contract_violation",
543        ],
544    }
545}
546
547fn format_flag() -> FlagContract {
548    FlagContract::value("--format", "json|text").allowed(vec!["json", "text"])
549}
550
551fn ai_flag() -> FlagContract {
552    FlagContract::value("--ai", "auto|daemon|direct|off")
553        .allowed(vec!["auto", "daemon", "direct", "off"])
554}
555
556fn ai_depth_flag() -> FlagContract {
557    FlagContract::value("--ai-depth", "sections|files|symbols")
558        .allowed(vec!["sections", "files", "symbols"])
559}
560
561fn ai_prose_depth_flag() -> FlagContract {
562    FlagContract::value("--ai-prose-depth", "brief|standard|deep")
563        .allowed(vec!["brief", "standard", "deep"])
564}
565
566fn ai_register_flag() -> FlagContract {
567    FlagContract::value("--ai-register", "newcomer|maintainer|agent").allowed(vec![
568        "newcomer",
569        "maintainer",
570        "agent",
571    ])
572}
573
574fn token_budget_flag() -> FlagContract {
575    FlagContract::value("--token-budget", "N")
576}
577
578fn search_flags() -> Vec<FlagContract> {
579    vec![
580        FlagContract::value("--limit", "N"),
581        FlagContract::value("--offset", "N"),
582        FlagContract::value("--kind", "KIND"),
583        FlagContract::value("--language", "LANG"),
584    ]
585}
586
587fn grep_flags() -> Vec<FlagContract> {
588    vec![
589        FlagContract::switch("--fixed-strings"),
590        FlagContract::switch("--ignore-case"),
591        FlagContract::switch("--word"),
592        FlagContract::value("--before-context", "N"),
593        FlagContract::value("--after-context", "N"),
594        FlagContract::value("--context", "N"),
595        FlagContract::repeatable_value("--glob", "GLOB"),
596        FlagContract::value("--max-count", "N"),
597        format_flag(),
598    ]
599}
600
601fn graph_read_flags() -> Vec<FlagContract> {
602    vec![
603        FlagContract::value("--limit", "N"),
604        FlagContract::value("--offset", "N"),
605        format_flag(),
606    ]
607}
608
609fn outline_keys() -> Vec<&'static str> {
610    vec!["id", "name", "kind", "line_start", "line_end", "signature"]
611}
612
613fn tree_keys() -> Vec<&'static str> {
614    vec!["file_path", "language", "symbol_count"]
615}
616
617/// The serialized fields of a stored symbol, shared by `symbol`, `symbol-at`,
618/// and `symbols`. `docstring`/`parent_symbol_id` are omitted: they serialize
619/// only when present, and the daemon-consumed surface relies on the AI
620/// `summary`, not the raw `docstring`.
621fn symbol_record_keys() -> Vec<&'static str> {
622    vec![
623        "id",
624        "project_id",
625        "file_path",
626        "name",
627        "qualified_name",
628        "kind",
629        "language",
630        "byte_start",
631        "byte_end",
632        "line_start",
633        "line_end",
634        "signature",
635        "content_hash",
636        "summary",
637        "created_at",
638        "updated_at",
639    ]
640}
641
642/// `symbol` wraps the record with the on-disk `source` snippet.
643fn symbol_keys() -> Vec<&'static str> {
644    let mut keys = symbol_record_keys();
645    keys.push("source");
646    keys
647}
648
649/// `symbol-at` adds a `lookup` block describing how the location resolved.
650fn symbol_at_keys() -> Vec<&'static str> {
651    let mut keys = symbol_keys();
652    keys.push("lookup");
653    keys
654}
655
656/// Paged graph-result envelope for `imports` and `blast-radius`. Kept distinct
657/// from [`graph_read_keys`] (callers/usages) so the two surfaces can diverge
658/// without coupling, even though they currently share the same key set.
659fn paged_graph_keys() -> Vec<&'static str> {
660    vec![
661        "project_id",
662        "total",
663        "offset",
664        "limit",
665        "results",
666        "id",
667        "name",
668        "file_path",
669        "line",
670        "confidence",
671        "relation",
672        "distance",
673        "metadata",
674        "hint",
675    ]
676}
677
678fn search_keys() -> Vec<&'static str> {
679    vec![
680        "project_id",
681        "total",
682        "offset",
683        "limit",
684        "results",
685        "id",
686        "name",
687        "qualified_name",
688        "kind",
689        "language",
690        "file_path",
691        "line_start",
692        "line_end",
693        "signature",
694        "score",
695    ]
696}
697
698fn grep_keys() -> Vec<&'static str> {
699    vec![
700        "project_id",
701        "pattern",
702        "fixed_strings",
703        "ignore_case",
704        "word",
705        "paths",
706        "globs",
707        "max_count",
708        "matched_lines",
709        "truncated",
710        "scanned_chunks",
711        "matches",
712        "path",
713        "line",
714        "text",
715        "spans",
716        "start",
717        "end",
718        "before",
719        "after",
720    ]
721}
722
723fn graph_read_keys() -> Vec<&'static str> {
724    vec![
725        "project_id",
726        "total",
727        "offset",
728        "limit",
729        "results",
730        "id",
731        "name",
732        "file_path",
733        "line",
734        "confidence",
735        "relation",
736        "distance",
737        "metadata",
738        "hint",
739    ]
740}
741
742fn graph_path_keys() -> Vec<&'static str> {
743    vec![
744        "project_id",
745        "found",
746        "from",
747        "to",
748        "max_depth",
749        "hops",
750        "path",
751        "position",
752        "id",
753        "display_name",
754        "name",
755        "file_path",
756        "line",
757        "hint",
758    ]
759}
760
761fn contract_keys() -> Vec<&'static str> {
762    vec![
763        "tool",
764        "contract_version",
765        "summary",
766        "global_flags",
767        "scope",
768        "commands",
769        "error_codes",
770    ]
771}
772
773fn graph_payload_keys() -> Vec<&'static str> {
774    vec!["nodes", "links", "summary"]
775}
776
777fn graph_lifecycle_keys() -> Vec<&'static str> {
778    vec![
779        "status",
780        "action",
781        "project_id",
782        "synced_files",
783        "synced_symbols",
784        "skipped_files",
785        "failed_files",
786        "synced_relationships",
787        "deleted_nodes",
788        "deleted_relationships",
789        "summary",
790    ]
791}
792
793fn graph_cleanup_keys() -> Vec<&'static str> {
794    vec![
795        "status",
796        "action",
797        "project_id",
798        "stale_graph_files_deleted",
799        "graph_nodes_deleted",
800    ]
801}
802
803fn graph_report_keys() -> Vec<&'static str> {
804    vec!["project_id", "summary", "hotspots", "bridges", "degraded"]
805}
806
807fn vector_lifecycle_keys() -> Vec<&'static str> {
808    vec![
809        "success",
810        "status",
811        "project_id",
812        "projection",
813        "action",
814        "file_path",
815        "collection",
816        "synced_files",
817        "synced_symbols",
818        "skipped_files",
819        "failed_files",
820        "symbols",
821        "vectors_upserted",
822        "delete_operations_issued",
823        "degraded",
824        "error",
825        "summary",
826    ]
827}
828
829fn vector_cleanup_keys() -> Vec<&'static str> {
830    vec![
831        "project_id",
832        "projection",
833        "action",
834        "collection",
835        "status",
836        "vector_files_scanned",
837        "orphan_files_deleted",
838        "vectors_deleted",
839        "summary",
840    ]
841}
842
843fn embeddings_doctor_keys() -> Vec<&'static str> {
844    vec![
845        "status",
846        "project_id",
847        "source",
848        "model",
849        "vector_dim",
850        "peer",
851        "drift",
852    ]
853}