forge_pilot/bootstrap/
mod.rs1mod 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}