From 0302b4596e14210884df5d645df9a179d8f0c1c6 Mon Sep 17 00:00:00 2001 From: soryu Date: Thu, 5 Feb 2026 00:48:38 +0000 Subject: Add multi-repository support for chains Chains can now have multiple repositories attached, with one marked as primary. Repositories are used by contracts created from chain definitions. Backend changes: - Add chain_repositories table migration - Add ChainRepository model with CRUD operations - Add API endpoints for listing, adding, deleting repositories - Add endpoint to set a repository as primary - Update Chain and ChainEditorData models to use repositories - Update chain parser to support repositories in YAML format - Remove deprecated repository_url/local_path from Chain Frontend changes: - Add ChainRepository interface and API functions - Add repository section to ChainEditor showing attached repos - Add modal for adding new repositories (remote or local) - Support setting primary repository and removing repositories Co-Authored-By: Claude Opus 4.5 --- makima/src/daemon/chain/parser.rs | 29 ++++++++++++++++++++++++----- makima/src/daemon/chain/runner.rs | 24 ++++++++++++++++++++---- 2 files changed, 44 insertions(+), 9 deletions(-) (limited to 'makima/src/daemon/chain') diff --git a/makima/src/daemon/chain/parser.rs b/makima/src/daemon/chain/parser.rs index 0f16710..3851d1f 100644 --- a/makima/src/daemon/chain/parser.rs +++ b/makima/src/daemon/chain/parser.rs @@ -20,6 +20,27 @@ pub enum ParseError { ValidationError(String), } +/// Repository definition in a chain. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RepositoryDefinition { + /// Name of the repository + pub name: String, + /// Repository URL (for remote repos) + pub repository_url: Option, + /// Local path (for local repos) + pub local_path: Option, + /// Source type: remote, local, or managed + #[serde(default = "default_source_type")] + pub source_type: String, + /// Whether this is the primary repository + #[serde(default)] + pub is_primary: bool, +} + +fn default_source_type() -> String { + "remote".to_string() +} + /// Chain definition parsed from YAML. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChainDefinition { @@ -27,11 +48,9 @@ pub struct ChainDefinition { pub name: String, /// Optional description pub description: Option, - /// Repository URL (optional - contracts may have their own repos) - #[serde(alias = "repo")] - pub repository_url: Option, - /// Local path for repository - pub local_path: Option, + /// Repositories for this chain + #[serde(default)] + pub repositories: Vec, /// Contracts in this chain pub contracts: Vec, /// Loop configuration diff --git a/makima/src/daemon/chain/runner.rs b/makima/src/daemon/chain/runner.rs index 9c6f6b4..dfbcfa7 100644 --- a/makima/src/daemon/chain/runner.rs +++ b/makima/src/daemon/chain/runner.rs @@ -14,8 +14,8 @@ use thiserror::Error; use super::dag::{topological_sort, validate_dag, DagError}; use super::parser::{parse_chain_file, ChainDefinition, ParseError}; use crate::db::models::{ - CreateChainContractRequest, CreateChainDeliverableRequest, CreateChainRequest, - CreateChainTaskRequest, + AddChainRepositoryRequest, CreateChainContractRequest, CreateChainDeliverableRequest, + CreateChainRequest, CreateChainTaskRequest, }; /// Error type for chain runner operations. @@ -100,11 +100,27 @@ impl ChainRunner { None => (None, None, None), }; + // Convert repository definitions to API format + let repositories: Vec = chain + .repositories + .iter() + .map(|r| AddChainRepositoryRequest { + name: r.name.clone(), + repository_url: r.repository_url.clone(), + local_path: r.local_path.clone(), + source_type: r.source_type.clone(), + is_primary: r.is_primary, + }) + .collect(); + CreateChainRequest { name: chain.name.clone(), description: chain.description.clone(), - repository_url: chain.repository_url.clone(), - local_path: chain.local_path.clone(), + repositories: if repositories.is_empty() { + None + } else { + Some(repositories) + }, loop_enabled, loop_max_iterations, loop_progress_check, -- cgit v1.2.3