Skip to content

Commit ff0fbba

Browse files
userFRMclaude
andauthored
fix: release CI — macOS Intel OpenSSL, aarch64-linux feature gating (#80)
Two release build failures fixed: 1. x86_64-apple-darwin: macos-latest is ARM64, can't cross-compile OpenSSL for x86_64. Fixed by using macos-13 (still x86_64 native). 2. aarch64-unknown-linux-gnu: --no-default-features disables auto-lift but #[tool_router] proc macro still tries to register the method. Fixed by removing #[cfg] from the tool/params/field (always compile) and gating only the method body (returns error when feature disabled). Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 09b04aa commit ff0fbba

4 files changed

Lines changed: 122 additions & 116 deletions

File tree

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- target: aarch64-apple-darwin
2323
os: macos-latest
2424
- target: x86_64-apple-darwin
25-
os: macos-latest
25+
os: macos-13
2626
- target: x86_64-unknown-linux-gnu
2727
os: ubuntu-latest
2828
- target: x86_64-pc-windows-msvc

crates/rpg-mcp/src/params.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,6 @@ pub(crate) struct DetectCyclesParams {
264264
}
265265

266266
/// Parameters for the `auto_lift` tool.
267-
#[cfg(feature = "auto-lift")]
268267
#[derive(Deserialize, JsonSchema)]
269268
pub(crate) struct AutoLiftParams {
270269
/// LLM provider: "anthropic", "openai", or any OpenAI-compatible endpoint.
@@ -283,7 +282,6 @@ pub(crate) struct AutoLiftParams {
283282
pub(crate) dry_run: Option<bool>,
284283
}
285284

286-
#[cfg(feature = "auto-lift")]
287285
impl std::fmt::Debug for AutoLiftParams {
288286
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
289287
f.debug_struct("AutoLiftParams")

crates/rpg-mcp/src/server.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ pub(crate) struct RpgServer {
5454
/// Last git HEAD SHA at which auto-sync ran. Prevents redundant updates.
5555
pub(crate) last_auto_sync_head: Arc<RwLock<Option<String>>>,
5656
/// Guard: true while auto_lift is running. Rejects concurrent lift calls.
57-
#[cfg(feature = "auto-lift")]
5857
pub(crate) lift_in_progress: Arc<std::sync::atomic::AtomicBool>,
5958
}
6059

@@ -91,7 +90,6 @@ impl RpgServer {
9190
tool_router: Self::create_tool_router(),
9291
prompt_versions: PromptVersions::new(),
9392
last_auto_sync_head: Arc::new(RwLock::new(initial_head)),
94-
#[cfg(feature = "auto-lift")]
9593
lift_in_progress: Arc::new(std::sync::atomic::AtomicBool::new(false)),
9694
}
9795
}

crates/rpg-mcp/src/tools.rs

Lines changed: 121 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,6 @@ impl RpgServer {
708708
Ok(result)
709709
}
710710

711-
#[cfg(feature = "auto-lift")]
712711
#[tool(
713712
description = "Autonomous lifting via LLM API — lifts all unlifted entities, synthesizes file features, and builds semantic hierarchy in one call. Uses a cheap external LLM (Haiku, GPT-4o-mini) instead of consuming your coding agent's subscription tokens. Providers: 'anthropic' (default model: claude-haiku-4-5), 'openai' (default: gpt-4o-mini). For OpenRouter or Gemini, use provider='openai' with a custom base_url. Set dry_run=true to estimate cost first. Requires an API key.",
714713
annotations(
@@ -719,113 +718,121 @@ impl RpgServer {
719718
)]
720719
async fn auto_lift(
721720
&self,
722-
Parameters(params): Parameters<AutoLiftParams>,
721+
#[allow(unused_variables)] Parameters(params): Parameters<AutoLiftParams>,
723722
) -> Result<String, String> {
724-
/// Drop guard that clears the `lift_in_progress` flag on scope exit.
725-
struct LiftGuard(std::sync::Arc<std::sync::atomic::AtomicBool>);
726-
impl Drop for LiftGuard {
727-
fn drop(&mut self) {
728-
self.0.store(false, std::sync::atomic::Ordering::SeqCst);
729-
}
723+
#[cfg(not(feature = "auto-lift"))]
724+
{
725+
return Err("auto_lift is not available: this binary was compiled without the auto-lift feature.".into());
730726
}
731727

732-
self.ensure_graph().await?;
733-
734-
// Reject concurrent lift calls
735-
if self
736-
.lift_in_progress
737-
.swap(true, std::sync::atomic::Ordering::SeqCst)
728+
#[cfg(feature = "auto-lift")]
738729
{
739-
return Err("A lift is already in progress. Wait for it to complete.".into());
740-
}
741-
let _guard = LiftGuard(std::sync::Arc::clone(&self.lift_in_progress));
730+
/// Drop guard that clears the `lift_in_progress` flag on scope exit.
731+
struct LiftGuard(std::sync::Arc<std::sync::atomic::AtomicBool>);
732+
impl Drop for LiftGuard {
733+
fn drop(&mut self) {
734+
self.0.store(false, std::sync::atomic::Ordering::SeqCst);
735+
}
736+
}
742737

743-
// Resolve API key: prefer api_key_env (safe), fall back to api_key (raw)
744-
let api_key = if let Some(ref env_var) = params.api_key_env {
745-
std::env::var(env_var).map_err(|_| {
746-
format!(
747-
"Environment variable '{}' not set. Set it or use api_key instead.",
748-
env_var
749-
)
750-
})?
751-
} else if let Some(ref key) = params.api_key {
752-
key.clone()
753-
} else {
754-
// Auto-detect from standard env vars based on provider
755-
let env_var = match params.provider.as_str() {
756-
"anthropic" => "ANTHROPIC_API_KEY",
757-
"openai" => "OPENAI_API_KEY",
758-
_ => "OPENAI_API_KEY",
759-
};
760-
std::env::var(env_var).map_err(|_| {
738+
self.ensure_graph().await?;
739+
740+
// Reject concurrent lift calls
741+
if self
742+
.lift_in_progress
743+
.swap(true, std::sync::atomic::Ordering::SeqCst)
744+
{
745+
return Err("A lift is already in progress. Wait for it to complete.".into());
746+
}
747+
let _guard = LiftGuard(std::sync::Arc::clone(&self.lift_in_progress));
748+
749+
// Resolve API key: prefer api_key_env (safe), fall back to api_key (raw)
750+
let api_key = if let Some(ref env_var) = params.api_key_env {
751+
std::env::var(env_var).map_err(|_| {
752+
format!(
753+
"Environment variable '{}' not set. Set it or use api_key instead.",
754+
env_var
755+
)
756+
})?
757+
} else if let Some(ref key) = params.api_key {
758+
key.clone()
759+
} else {
760+
// Auto-detect from standard env vars based on provider
761+
let env_var = match params.provider.as_str() {
762+
"anthropic" => "ANTHROPIC_API_KEY",
763+
"openai" => "OPENAI_API_KEY",
764+
_ => "OPENAI_API_KEY",
765+
};
766+
std::env::var(env_var).map_err(|_| {
761767
format!(
762768
"No API key provided. Use api_key_env=\"{}\" or api_key, or set {} env var.",
763769
env_var, env_var
764770
)
765771
})?
766-
};
772+
};
767773

768-
let provider = rpg_lift::create_provider(
769-
&params.provider,
770-
&api_key,
771-
params.model.as_deref(),
772-
params.base_url.as_deref(),
773-
)
774-
.map_err(|e| format!("Failed to create LLM provider: {}", e))?;
774+
let provider = rpg_lift::create_provider(
775+
&params.provider,
776+
&api_key,
777+
params.model.as_deref(),
778+
params.base_url.as_deref(),
779+
)
780+
.map_err(|e| format!("Failed to create LLM provider: {}", e))?;
775781

776-
let scope = params.scope.as_deref().unwrap_or("*");
777-
let dry_run = params.dry_run.unwrap_or(false);
782+
let scope = params.scope.as_deref().unwrap_or("*");
783+
let dry_run = params.dry_run.unwrap_or(false);
778784

779-
// Dry run: estimate cost without lifting
780-
if dry_run {
781-
let guard = self.graph.read().await;
782-
let graph = guard.as_ref().unwrap();
783-
let estimate = rpg_lift::estimate_cost(graph, provider.as_ref(), &self.project_root);
784-
return Ok(format!(
785-
"Cost estimate for lifting with {} ({}):\n\n{}",
786-
params.provider,
787-
provider.model_name(),
788-
estimate,
789-
));
790-
}
785+
// Dry run: estimate cost without lifting
786+
if dry_run {
787+
let guard = self.graph.read().await;
788+
let graph = guard.as_ref().unwrap();
789+
let estimate =
790+
rpg_lift::estimate_cost(graph, provider.as_ref(), &self.project_root);
791+
return Ok(format!(
792+
"Cost estimate for lifting with {} ({}):\n\n{}",
793+
params.provider,
794+
provider.model_name(),
795+
estimate,
796+
));
797+
}
791798

792-
// Hold the write lock for the entire pipeline. The graph never leaves
793-
// shared state, so cancellation or concurrent tools can't corrupt it.
794-
let mut guard = self.graph.write().await;
795-
let graph = guard.as_mut().ok_or("No RPG loaded")?;
799+
// Hold the write lock for the entire pipeline. The graph never leaves
800+
// shared state, so cancellation or concurrent tools can't corrupt it.
801+
let mut guard = self.graph.write().await;
802+
let graph = guard.as_mut().ok_or("No RPG loaded")?;
803+
804+
let project_root = self.project_root.clone();
805+
let scope_owned = scope.to_string();
806+
807+
// Run the blocking pipeline on the current thread (tells tokio we're blocking).
808+
// This is safe because MCP stdio is serial — no concurrent requests.
809+
let report = tokio::task::block_in_place(|| {
810+
let config = rpg_lift::LiftConfig {
811+
provider: provider.as_ref(),
812+
project_root: &project_root,
813+
scope: &scope_owned,
814+
max_retries: 2,
815+
batch_size: 25,
816+
batch_tokens: 8000,
817+
};
818+
let result = rpg_lift::run_pipeline(graph, &config);
819+
let _ = rpg_core::storage::save(&project_root, graph);
820+
result
821+
})
822+
.map_err(|e| format!("Lift failed: {}", e))?;
796823

797-
let project_root = self.project_root.clone();
798-
let scope_owned = scope.to_string();
799-
800-
// Run the blocking pipeline on the current thread (tells tokio we're blocking).
801-
// This is safe because MCP stdio is serial — no concurrent requests.
802-
let report = tokio::task::block_in_place(|| {
803-
let config = rpg_lift::LiftConfig {
804-
provider: provider.as_ref(),
805-
project_root: &project_root,
806-
scope: &scope_owned,
807-
max_retries: 2,
808-
batch_size: 25,
809-
batch_tokens: 8000,
810-
};
811-
let result = rpg_lift::run_pipeline(graph, &config);
812-
let _ = rpg_core::storage::save(&project_root, graph);
813-
result
814-
})
815-
.map_err(|e| format!("Lift failed: {}", e))?;
824+
drop(guard);
816825

817-
drop(guard);
826+
// Clear sessions — entity list changed
827+
*self.lifting_session.write().await = None;
828+
*self.hierarchy_session.write().await = None;
818829

819-
// Clear sessions — entity list changed
820-
*self.lifting_session.write().await = None;
821-
*self.hierarchy_session.write().await = None;
822-
823-
// Update auto-sync HEAD
824-
*self.last_auto_sync_head.write().await =
825-
rpg_encoder::evolution::get_head_sha(&self.project_root).ok();
830+
// Update auto-sync HEAD
831+
*self.last_auto_sync_head.write().await =
832+
rpg_encoder::evolution::get_head_sha(&self.project_root).ok();
826833

827-
let mut out = format!(
828-
"Lifting complete ({}, {}).\n\
834+
let mut out = format!(
835+
"Lifting complete ({}, {}).\n\
829836
auto_lifted: {}\n\
830837
llm_lifted: {}\n\
831838
failed: {}\n\
@@ -834,29 +841,32 @@ impl RpgServer {
834841
hierarchy: {}\n\
835842
tokens: {} in / {} out\n\
836843
cost: ${:.4}",
837-
params.provider,
838-
report.total_input_tokens + report.total_output_tokens,
839-
report.entities_auto_lifted,
840-
report.entities_llm_lifted,
841-
report.entities_failed,
842-
report.batches_processed,
843-
report.files_synthesized,
844-
if report.hierarchy_assigned {
845-
"assigned"
846-
} else {
847-
"not assigned"
848-
},
849-
report.total_input_tokens,
850-
report.total_output_tokens,
851-
report.total_cost_usd,
852-
);
844+
params.provider,
845+
report.total_input_tokens + report.total_output_tokens,
846+
report.entities_auto_lifted,
847+
report.entities_llm_lifted,
848+
report.entities_failed,
849+
report.batches_processed,
850+
report.files_synthesized,
851+
if report.hierarchy_assigned {
852+
"assigned"
853+
} else {
854+
"not assigned"
855+
},
856+
report.total_input_tokens,
857+
report.total_output_tokens,
858+
report.total_cost_usd,
859+
);
853860

854-
if !report.errors.is_empty() {
855-
out.push_str(&format!("\nerrors: {}", report.errors.join("; ")));
856-
}
861+
if !report.errors.is_empty() {
862+
out.push_str(&format!("\nerrors: {}", report.errors.join("; ")));
863+
}
857864

858-
out.push_str("\n\nNEXT STEP: Call semantic_snapshot to see the full repo understanding.");
859-
Ok(out)
865+
out.push_str(
866+
"\n\nNEXT STEP: Call semantic_snapshot to see the full repo understanding.",
867+
);
868+
Ok(out)
869+
} // #[cfg(feature = "auto-lift")]
860870
}
861871

862872
#[tool(

0 commit comments

Comments
 (0)