Skip to main content

forge_pilot/bootstrap/
mod.rs

1mod capability;
2mod chunk;
3mod classify;
4mod current_state;
5mod delta;
6mod extract;
7mod ignore;
8mod import;
9pub(crate) mod manifest;
10mod materialize;
11mod pipeline;
12pub(crate) mod report;
13mod scan;
14pub(crate) mod types;
15
16use crate::config::LoopConfig;
17use crate::error::PilotError;
18use semantic_memory::MemoryStore;
19use std::path::PathBuf;
20
21pub(crate) use classify::is_supported_source_file_path;
22pub(crate) use current_state::{
23    current_state_from_latest_manifest, manifest_delta_from_current_state,
24    manifest_from_current_state,
25};
26pub(crate) use ignore::should_skip_workspace_dir;
27pub use manifest::{compute_manifest_delta, manifest_from_batch};
28pub(crate) use report::is_auxiliary_bootstrap_claim;
29pub use types::{
30    BootstrapCurrentState, BootstrapDeltaSummary, BootstrapManifestDelta,
31    BootstrapManifestSnapshot, BootstrapRichness, BootstrapSourceObservation,
32    BootstrapSourceReport, BootstrapSourceRichness, BootstrapSourceSkippedFile, SymbolRecord,
33};
34
35pub async fn bootstrap_source_workspace(
36    memory_store: &MemoryStore,
37    config: &LoopConfig,
38) -> Result<BootstrapSourceReport, PilotError> {
39    let workspace_path = resolve_runtime_path(&config.workspace_path);
40    let pipeline =
41        pipeline::build_bootstrap_pipeline(memory_store, config, &workspace_path).await?;
42    if pipeline.prepared_files.is_empty() && pipeline.previous_manifest.is_none() {
43        return Ok(report::build_report(
44            &config.scope.namespace,
45            workspace_path.to_string_lossy().to_string(),
46            0,
47            pipeline.scan.skipped_files,
48            None,
49            None,
50            None,
51            0,
52            0,
53            None,
54            None,
55        ));
56    }
57
58    if pipeline.delta.is_noop() {
59        let import_result = semantic_memory::ProjectionImportResult {
60            source_envelope_id: format!(
61                "workspace-source-envelope:{}",
62                pipeline.current_manifest.manifest_id
63            ),
64            status: "already_imported".into(),
65            record_count: 0,
66            was_duplicate: true,
67        };
68        return Ok(report::build_report(
69            &config.scope.namespace,
70            workspace_path.to_string_lossy().to_string(),
71            pipeline.prepared_files.len(),
72            pipeline.scan.skipped_files,
73            Some(&pipeline.current_manifest),
74            Some(&pipeline.delta),
75            Some(&import_result),
76            0,
77            0,
78            Some(import_result.source_envelope_id.clone()),
79            None,
80        ));
81    }
82    let materialized = materialize::materialize_records(
83        &pipeline.current_manifest,
84        pipeline.previous_manifest.as_ref(),
85        &pipeline.delta,
86        &pipeline.prepared_files,
87    )?;
88    let envelope = import::build_source_envelope(
89        &config.scope.namespace,
90        &pipeline.current_manifest,
91        materialized.records,
92    )?;
93    let import_result = import::import_envelope(memory_store, &envelope).await?;
94
95    Ok(report::build_report(
96        &config.scope.namespace,
97        workspace_path.to_string_lossy().to_string(),
98        pipeline.prepared_files.len(),
99        pipeline.scan.skipped_files,
100        Some(&pipeline.current_manifest),
101        Some(&pipeline.delta),
102        Some(&import_result),
103        materialized.imported_chunk_count,
104        materialized.imported_symbol_count,
105        Some(envelope.envelope_id.as_str().to_string()),
106        Some(envelope.content_digest.hex().to_string()),
107    ))
108}
109fn resolve_runtime_path(raw: &str) -> PathBuf {
110    let path = PathBuf::from(raw);
111    if path.is_absolute() {
112        path
113    } else {
114        std::env::current_dir()
115            .unwrap_or_else(|_| PathBuf::from("."))
116            .join(path)
117    }
118}