Skip to main content

gobby_code/commands/
init.rs

1use std::path::Path;
2
3use crate::config;
4use crate::db;
5use crate::index::api;
6use crate::index_lock::{self, IndexLockPolicy, IndexLockResult};
7use crate::output::{self, Format};
8use crate::project;
9use crate::skill;
10
11pub fn run(project_root: &Path, format: Format, quiet: bool) -> anyhow::Result<()> {
12    let identity =
13        config::resolve_project_identity(project_root, config::MissingIdentity::Generate)?;
14    config::warn_project_identity(&identity, quiet);
15    let (project_id, was_created) = if identity.should_write_gcode_json {
16        project::ensure_gcode_json(project_root)?
17    } else {
18        (identity.project_id.clone(), false)
19    };
20
21    let status = match identity.source {
22        config::ProjectIdentitySource::IsolatedRoot => "isolated",
23        config::ProjectIdentitySource::LinkedWorktree => "linked-worktree",
24        config::ProjectIdentitySource::ProjectJson => "gobby",
25        config::ProjectIdentitySource::Generated if was_created => "initialized",
26        _ => "existing",
27    };
28
29    // Install AI CLI skills (skip if Gobby manages this project)
30    let mut installed_skills: Vec<String> = Vec::new();
31    if status != "gobby" {
32        for target in skill::supported_targets() {
33            match skill::install_skill(project_root, target) {
34                Ok(path) if !path.is_empty() => {
35                    if !quiet {
36                        eprintln!(
37                            "Installed gcode skill for {} → {}",
38                            target.display_name, path
39                        );
40                    }
41                    installed_skills.push(target.display_name.to_string());
42                }
43                Err(e) if !quiet => {
44                    eprintln!(
45                        "Warning: failed to install skill for {}: {}",
46                        target.display_name, e
47                    );
48                }
49                _ => {}
50            }
51        }
52    }
53
54    // Auto-index the project. The daemon process is not required, but a migrated
55    // PostgreSQL hub must already be configured in Gobby bootstrap.
56    let database_url = db::resolve_database_url()?;
57    let index_ctx = config::Context {
58        database_url,
59        project_root: project_root.to_path_buf(),
60        project_id: project_id.clone(),
61        quiet,
62        falkordb: None,
63        qdrant: None,
64        embedding: None,
65        code_vectors: config::CodeVectorSettings::default(),
66        indexing: gobby_core::config::IndexingConfig::default(),
67        daemon_url: None,
68        index_scope: config::ProjectIndexScope::Single,
69    };
70    let index_result =
71        match index_lock::with_project_lock(&index_ctx, IndexLockPolicy::Wait, || {
72            api::index_files(
73                api::IndexRequest {
74                    project_root: project_root.to_path_buf(),
75                    path_filter: None,
76                    explicit_files: Vec::new(),
77                    full: false,
78                    require_cpp_semantics: false,
79                    sync_projections: false,
80                },
81                &index_ctx,
82            )
83        })? {
84            IndexLockResult::Acquired(outcome) => outcome,
85            IndexLockResult::Busy => unreachable!("wait policy always acquires the index lock"),
86        };
87    if !quiet {
88        eprintln!(
89            "Indexed {} files, {} symbols in {}ms",
90            index_result.indexed_files,
91            index_result.symbols_indexed,
92            index_result.durations.total_ms
93        );
94    }
95
96    match format {
97        Format::Json => {
98            let mut result = serde_json::json!({
99                "project_id": project_id,
100                "project_root": project_root.to_string_lossy(),
101                "status": status,
102                "files_indexed": index_result.indexed_files,
103                "symbols_found": index_result.symbols_indexed,
104                "duration_ms": index_result.durations.total_ms,
105            });
106            if !installed_skills.is_empty() {
107                result["skills_installed"] = serde_json::json!(installed_skills);
108            }
109            output::print_json(&result)
110        }
111        Format::Text => {
112            if !quiet {
113                match status {
114                    "initialized" => {
115                        eprintln!(
116                            "Initialized project at {}\nProject ID: {}",
117                            project_root.display(),
118                            project_id
119                        );
120                    }
121                    "gobby" => {
122                        eprintln!(
123                            "Using gobby project: {} ({})",
124                            project_id,
125                            project_root.display()
126                        );
127                    }
128                    "isolated" | "linked-worktree" => {
129                        eprintln!(
130                            "Using {} code index: {} ({})",
131                            status,
132                            project_id,
133                            project_root.display()
134                        );
135                    }
136                    _ => {
137                        eprintln!(
138                            "Already initialized: {} ({})",
139                            project_id,
140                            project_root.display()
141                        );
142                    }
143                }
144            }
145            Ok(())
146        }
147    }
148}