1use gobby_core::cli_contract::{
2 CliContract, CommandContract, FlagContract, PositionalContract, ScopeContract,
3};
4
5pub fn contract() -> CliContract {
6 CliContract {
7 tool: "gcode",
8 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 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
617fn 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
642fn symbol_keys() -> Vec<&'static str> {
644 let mut keys = symbol_record_keys();
645 keys.push("source");
646 keys
647}
648
649fn symbol_at_keys() -> Vec<&'static str> {
651 let mut keys = symbol_keys();
652 keys.push("lookup");
653 keys
654}
655
656fn 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}