pub(crate) mod alc_toml;
mod config;
mod engine_api_impl;
mod eval;
mod eval_store;
mod init;
pub(crate) mod lockfile;
mod logging;
pub(crate) mod manifest;
mod migrate;
pub(crate) mod path;
mod pkg;
mod pkg_link;
mod pkg_unlink;
pub(crate) mod project;
pub mod resolve;
mod run;
mod scenario;
pub(crate) mod source;
mod status;
mod transcript;
mod update;
#[cfg(test)]
mod test_support;
#[cfg(test)]
mod tests;
use std::path::Path;
use std::sync::Arc;
use algocline_engine::{Executor, SessionRegistry};
pub use algocline_core::{EngineApi, TokenUsage};
pub use config::{AppConfig, LogDirSource};
pub use resolve::{QueryResponse, SearchPath};
type EvalSessions = std::sync::Mutex<std::collections::HashMap<String, String>>;
type SessionStrategies = std::sync::Mutex<std::collections::HashMap<String, String>>;
#[derive(Clone)]
pub struct AppService {
executor: Arc<Executor>,
registry: Arc<SessionRegistry>,
log_config: AppConfig,
search_paths: Vec<resolve::SearchPath>,
eval_sessions: Arc<EvalSessions>,
session_strategies: Arc<SessionStrategies>,
}
impl AppService {
pub fn new(
executor: Arc<Executor>,
log_config: AppConfig,
search_paths: Vec<resolve::SearchPath>,
) -> Self {
let registry = Arc::new(SessionRegistry::new());
registry.spawn_gc_task(std::time::Duration::from_secs(10800));
Self {
executor,
registry,
log_config,
search_paths,
eval_sessions: Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())),
session_strategies: Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())),
}
}
fn require_log_dir(&self) -> Result<&Path, String> {
self.log_config
.log_dir
.as_deref()
.ok_or_else(|| "File logging is not available (no writable log directory)".to_string())
}
pub(crate) fn resolve_extra_lib_paths(
&self,
project_root: Option<&str>,
) -> Vec<std::path::PathBuf> {
let Some(root) = project::resolve_project_root(project_root) else {
return vec![];
};
match lockfile::load_lockfile(&root) {
Ok(Some(lock)) => {
self.warn_toml_lock_mismatch(&root, &lock);
lockfile::resolve_path_entries(&root, &lock)
}
Ok(None) => vec![],
Err(e) => {
tracing::warn!("failed to load alc.lock at {}: {e}", root.display());
vec![]
}
}
}
fn warn_toml_lock_mismatch(&self, root: &Path, lock: &lockfile::LockFile) {
let toml = match alc_toml::load_alc_toml(root) {
Ok(Some(t)) => t,
_ => return,
};
use std::collections::BTreeSet;
let toml_names: BTreeSet<&str> = toml.packages.keys().map(|s| s.as_str()).collect();
let lock_names: BTreeSet<&str> = lock.packages.iter().map(|p| p.name.as_str()).collect();
for name in toml_names.difference(&lock_names) {
eprintln!(
"warning: '{name}' is declared in alc.toml but missing from alc.lock. Run `alc_update` to sync."
);
}
for name in lock_names.difference(&toml_names) {
eprintln!("warning: '{name}' is in alc.lock but not declared in alc.toml.");
}
}
}