use crate::projects::join::JoinState;
use std::path::PathBuf;
mod ensure;
mod exec;
mod goal;
mod identity;
pub mod instructions;
mod prompt;
mod run;
#[cfg(test)]
mod tests;
pub use ensure::execute_ensure_workspace;
pub use exec::{
ClaimResolved, DETACHED_EXIT, Deps, Prepared, StartError, cross_check_claim, execute_claim,
execute_create, on_mint,
};
pub use goal::{Composer, parse_ball_stamp, preview};
pub use identity::{identity_preview, parse_identity_stamp, strip_identity_stamp};
pub use prompt::{Fire, execute_prompt};
pub use run::{prepare, resolve_worktree};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BallSpec {
Existing {
id: String,
title: String,
body: String,
join: JoinState,
},
New {
title: String,
body: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Payload {
Bare,
Path { dir: PathBuf },
Ball { project: String, ball: BallSpec },
}
impl Payload {
pub fn project(&self) -> Option<String> {
match self {
Self::Ball { project, .. } => Some(project.clone()),
Self::Bare | Self::Path { .. } => None,
}
}
pub fn origin(&self) -> crate::opslog::Origin {
match self {
Self::Ball { .. } => crate::opslog::Origin::Balls,
Self::Bare | Self::Path { .. } => crate::opslog::Origin::Conversation,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StartInputs {
pub workspace: PathBuf,
pub payload: Payload,
pub repo: Option<PathBuf>,
pub home: PathBuf,
pub yog_data_root: PathBuf,
pub balls_state_root: PathBuf,
pub conversation_names: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Step {
EnsureSeeded,
EnsureWorkspace { workspace: PathBuf },
Create {
project: PathBuf,
title: String,
body: String,
},
Claim {
project: PathBuf,
id: String,
name: String,
},
Prompt {
workspace: String,
binding: Option<PathBuf>,
goal: String,
},
}
pub fn plan(inputs: &StartInputs) -> Vec<Step> {
let workspace = inputs.workspace.clone();
let mut steps = vec![
Step::EnsureSeeded,
Step::EnsureWorkspace {
workspace: workspace.clone(),
},
];
match &inputs.payload {
Payload::Ball {
project,
ball: BallSpec::New { title, body },
} => {
let _ = project;
steps.push(Step::Create {
project: inputs.repo.clone().unwrap_or_default(),
title: title.clone(),
body: body.clone(),
});
return steps;
}
Payload::Ball {
project,
ball: BallSpec::Existing { id, join, .. },
} if claim_needed(*join) => {
let _ = project;
steps.push(Step::Claim {
project: inputs.repo.clone().unwrap_or_default(),
id: id.clone(),
name: crate::naming::leaf(&inputs.workspace),
});
}
_ => {}
}
let worktree = goal::canonical_worktree(inputs);
let prepared = goal::compose_prepared(inputs, worktree.as_deref());
steps.push(Step::Prompt {
workspace: prepared.workspace,
binding: prepared.binding,
goal: prepared.goal,
});
steps
}
pub(crate) fn claim_needed(join: JoinState) -> bool {
matches!(join, JoinState::ReadyStartable)
}
pub fn is_start_eligible(state: JoinState) -> bool {
matches!(state, JoinState::ReadyStartable)
}
pub fn is_resume_eligible(state: JoinState) -> bool {
matches!(state, JoinState::Bound)
}