Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = ["crates/*"]
resolver = "2"

[workspace.package]
version = "0.1.201"
version = "0.1.202"
edition = "2024"
rust-version = "1.88"
license = "Apache-2.0"
Expand Down
24 changes: 24 additions & 0 deletions crates/cli-sub-agent/src/plan_cmd_steps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use tracing::{error, info, warn};
use csa_config::ProjectConfig;
use csa_core::types::ToolName;
use csa_executor::ModelSpec;
use csa_hooks::format_next_step_directive;
use weave::compiler::{ExecutionPlan, FailAction, PlanStep};

use super::plan_cmd_exec::{
Expand Down Expand Up @@ -267,6 +268,21 @@ pub(super) async fn execute_plan_with_journal(
persist_plan_journal(path, run_ctx.journal)?;
}

// Emit CSA:NEXT_STEP directive for pipeline chaining.
// On success: point to the next step in the plan.
// On failure: no directive (pipeline stops on abort).
if !is_failure
&& !result.skipped
&& let Some(next_step) = find_next_step(step, &plan.steps)
{
let cmd = format!(
"csa plan run --step {} \"{}\"",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Escape nested quotes before formatting NEXT_STEP directives

The plan path builds cmd with an embedded quoted title ("{}"), then format_next_step_directive wraps that whole command in cmd="..." again. Because the parser stops quoted values at the first " in parse_next_step_directive, emitted directives get truncated/malformed whenever a title is present, so downstream consumers cannot reliably parse the intended next command (and may miss required=true).

Useful? React with 👍 / 👎.

next_step.id, next_step.title
);
let required = matches!(next_step.on_fail, FailAction::Abort);
eprintln!("{}", format_next_step_directive(&cmd, required));
}

// Abort on failure when: on_fail=abort, or retry exhausted (retries
// already happened inside execute_step; reaching here means all failed),
// or delegate (unsupported in v1 — treated as abort).
Expand Down Expand Up @@ -615,3 +631,11 @@ pub(crate) fn should_inject_assignment_markers(step: &PlanStep) -> bool {
pub(crate) fn is_assignment_marker_key(key: &str) -> bool {
validate_variable_name(key).is_ok()
}

/// Find the next step in the plan after the current step.
///
/// Returns the first step with an ID greater than the current step's ID,
/// which is the sequential successor in a linear workflow.
fn find_next_step<'a>(current: &PlanStep, steps: &'a [PlanStep]) -> Option<&'a PlanStep> {
steps.iter().find(|s| s.id > current.id)
}
Comment on lines +639 to +641
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation of find_next_step relies on comparing step IDs, which assumes that IDs are strictly increasing and reflect the execution order. However, execute_plan_with_journal iterates through the steps in their list order. If the plan contains non-sequential IDs or is reordered, this logic may fail to identify the correct successor or miss it entirely. It is safer and more efficient to find the next step based on its position in the steps slice.

Suggested change
fn find_next_step<'a>(current: &PlanStep, steps: &'a [PlanStep]) -> Option<&'a PlanStep> {
steps.iter().find(|s| s.id > current.id)
}
fn find_next_step<'a>(current: &PlanStep, steps: &'a [PlanStep]) -> Option<&'a PlanStep> {
let idx = steps.iter().position(|s| std::ptr::eq(s, current))?;
steps.get(idx + 1)
}

21 changes: 21 additions & 0 deletions crates/cli-sub-agent/src/review_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ use csa_core::consensus::AgentResponse;
use csa_core::types::{OutputFormat, ToolName};
use csa_session::state::ReviewSessionMeta;

/// Next-step command emitted after a clean review verdict for pipeline chaining.
const NEXT_STEP_PR_BOT_CMD: &str = "csa plan run patterns/dev2merge/workflow.toml --step pr-bot";

#[path = "review_cmd_output.rs"]
mod output;
use output::{
Expand Down Expand Up @@ -374,6 +377,16 @@ pub(crate) async fn handle_review(args: ReviewArgs, current_depth: u32) -> Resul
],
&project_root,
);

// Emit CSA:NEXT_STEP directive for pipeline chaining.
// Orchestrators can parse this to mechanically chain review → pr-bot.
if verdict == CLEAN {
eprintln!(
"{}",
csa_hooks::format_next_step_directive(NEXT_STEP_PR_BOT_CMD, true,)
);
}

return Ok(effective_exit_code);
}

Expand Down Expand Up @@ -430,6 +443,14 @@ pub(crate) async fn handle_review(args: ReviewArgs, current_depth: u32) -> Resul
&project_root,
);

// Emit CSA:NEXT_STEP directive for pipeline chaining after fix loop.
if fix_passed {
eprintln!(
"{}",
csa_hooks::format_next_step_directive(NEXT_STEP_PR_BOT_CMD, true,)
);
}

return fix_exit_code;
}

Expand Down
Loading
Loading