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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ cargo run -p decodex --bin decodex -- lane steer <ISSUE> --run-id <RUN_ID> --exp
cargo run -p decodex --bin decodex -- research compile --intent "research X"
cargo run -p decodex --bin decodex -- research compile --input research-design-run.json
cargo run -p decodex --bin decodex -- research promote <CONTRACT_ID>
cargo run -p decodex --bin decodex -- intake issues --project decodex XY-1 XY-2 --dry-run
cargo run -p decodex --bin decodex -- radar refresh-upstream-queue
cargo run -p decodex --bin decodex -- radar refresh-release-delta
cargo run -p decodex --bin decodex -- radar validate
Expand Down
127 changes: 115 additions & 12 deletions apps/decodex/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use crate::{
LaneSteerRequest, RunOnceRequest, ServeRequest,
},
prelude::{Result, eyre},
program_intake::{self, IssueBatchIntakeCommandRequest},
radar::{
self, RadarBackfillReleaseRangeRequest, RadarBundleBuildRequest,
RadarBundleValidateRequest, RadarLedgerArtifactLinkRequest, RadarLedgerBootstrapRequest,
Expand Down Expand Up @@ -77,6 +78,7 @@ impl Cli {
Command::Diagnose(args) => args.run(),
Command::Evidence(args) => args.run(),
Command::Research(args) => args.run(),
Command::Intake(args) => args.run(),
Command::Recover(args) => args.run(),
Command::ArchiveLinear(args) => args.run(),
Command::Maintenance(args) => args.run(),
Expand Down Expand Up @@ -618,6 +620,61 @@ impl EvidenceCommand {
}
}

#[derive(Debug, Args)]
struct IntakeCommand {
#[command(subcommand)]
command: IntakeSubcommand,
}
impl IntakeCommand {
fn run(&self) -> Result<()> {
match &self.command {
IntakeSubcommand::Issues(args) => args.run(),
}
}
}

#[derive(Debug, Args)]
struct IntakeIssuesCommand {
#[command(flatten)]
project_config: ProjectConfigArgs,
/// Registered Decodex service id to intake against.
#[arg(long, value_name = "SERVICE_ID", conflicts_with = "config")]
project: Option<String>,
/// Read tracker state and print the deterministic intake report without local persistence.
#[arg(long, conflicts_with = "persist", required_unless_present = "persist")]
dry_run: bool,
/// Persist only local runtime Program Intake records; never mutates Linear queue labels.
#[arg(long, conflicts_with = "dry_run")]
persist: bool,
/// Emit structured JSON instead of human-readable text.
#[arg(long)]
json: bool,
/// Existing Linear issue identifiers to intake into an internal Execution Program.
#[arg(value_name = "ISSUE")]
#[arg(required = true)]
issues: Vec<String>,
}
impl IntakeIssuesCommand {
fn run(&self) -> Result<()> {
let report =
program_intake::run_issue_batch_intake_command(IssueBatchIntakeCommandRequest {
config_path: self.project_config.as_path(),
project_id: self.project.as_deref(),
issue_identifiers: self.issues.clone(),
dry_run: self.dry_run,
persist: self.persist,
})?;

if self.json {
println!("{}", serde_json::to_string_pretty(&report)?);
} else {
print!("{}", program_intake::render_issue_batch_intake_report(&report));
}

Ok(())
}
}

#[derive(Debug, Args)]
struct ResearchCommand {
#[command(flatten)]
Expand Down Expand Up @@ -1477,6 +1534,8 @@ enum Command {
Evidence(EvidenceCommand),
/// Compile or promote Decodex-native research/design contracts.
Research(ResearchCommand),
/// Operator issue-batch intake into internal Execution Programs, not a graph editor.
Intake(IntakeCommand),
/// Diagnose or explicitly repair supported retained-lane recovery cases.
Recover(RecoverCommand),
/// Dry-run or archive old terminal Linear issues by repo label.
Expand Down Expand Up @@ -1544,6 +1603,12 @@ enum ResearchSubcommand {
Promote(ResearchPromoteCommand),
}

#[derive(Debug, Subcommand)]
enum IntakeSubcommand {
/// Dry-run or persist existing Linear issues as an internal program intake batch.
Issues(IntakeIssuesCommand),
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
#[value(rename_all = "kebab-case")]
enum ResearchOutcomeArg {
Expand Down Expand Up @@ -1708,18 +1773,19 @@ mod tests {

use crate::cli::{
AccountCommand, AccountSubcommand, AccountUseCommand, AttemptCommand, Cli, Command,
CommitCommand, DiagnoseCommand, EvidenceCommand, LandCommand, LaneCommand,
LaneInspectCommand, LaneInterruptCommand, LaneSteerCommand, LaneSubcommand,
LegacyCloseoutRecoveryCommand, ProbeCommand, ProjectCommand, ProjectConfigArgs,
ProjectSubcommand, RadarBackfillReleaseRangeCommand, RadarBundleBuildCommand,
RadarBundleCommand, RadarBundleSubcommand, RadarBundleValidateCommand, RadarCommand,
RadarLedgerCommand, RadarLedgerIngestExistingCommand, RadarLedgerSubcommand,
RadarLedgerSummaryCommand, RadarRefreshReleaseDeltaCommand,
RadarRefreshUpstreamQueueCommand, RadarRenderSignalCommand, RadarSubcommand,
RadarValidateCommand, RecoverCommand, RecoverSubcommand, ResearchCommand,
ResearchCompileCommand, ResearchOutcomeArg, ResearchPromoteCommand, ResearchSubcommand,
ReviewHandoffDiagnoseCommand, ReviewHandoffRebindCommand, ReviewHandoffRecoveryCommand,
ReviewHandoffRecoverySubcommand, RunCommand, ServeCommand, StatusCommand,
CommitCommand, DiagnoseCommand, EvidenceCommand, IntakeCommand, IntakeIssuesCommand,
IntakeSubcommand, LandCommand, LaneCommand, LaneInspectCommand, LaneInterruptCommand,
LaneSteerCommand, LaneSubcommand, LegacyCloseoutRecoveryCommand, ProbeCommand,
ProjectCommand, ProjectConfigArgs, ProjectSubcommand, RadarBackfillReleaseRangeCommand,
RadarBundleBuildCommand, RadarBundleCommand, RadarBundleSubcommand,
RadarBundleValidateCommand, RadarCommand, RadarLedgerCommand,
RadarLedgerIngestExistingCommand, RadarLedgerSubcommand, RadarLedgerSummaryCommand,
RadarRefreshReleaseDeltaCommand, RadarRefreshUpstreamQueueCommand,
RadarRenderSignalCommand, RadarSubcommand, RadarValidateCommand, RecoverCommand,
RecoverSubcommand, ResearchCommand, ResearchCompileCommand, ResearchOutcomeArg,
ResearchPromoteCommand, ResearchSubcommand, ReviewHandoffDiagnoseCommand,
ReviewHandoffRebindCommand, ReviewHandoffRecoveryCommand, ReviewHandoffRecoverySubcommand,
RunCommand, ServeCommand, StatusCommand,
};

#[test]
Expand Down Expand Up @@ -2471,6 +2537,43 @@ mod tests {
));
}

#[test]
fn parses_intake_issues_dry_run_with_project() {
let cli = Cli::parse_from([
"decodex",
"intake",
"issues",
"--project",
"decodex",
"XY-1",
"XY-2",
"--dry-run",
"--json",
]);

assert!(matches!(
cli.command,
Command::Intake(IntakeCommand {
command: IntakeSubcommand::Issues(IntakeIssuesCommand {
project: Some(_),
dry_run: true,
persist: false,
json: true,
issues,
..
})
}) if issues == vec![String::from("XY-1"), String::from("XY-2")]
));
}

#[test]
fn rejects_intake_issues_without_explicit_mode() {
let error = Cli::try_parse_from(["decodex", "intake", "issues", "XY-1"])
.expect_err("intake issues requires dry-run or persist");

assert!(error.to_string().contains("--dry-run") || error.to_string().contains("--persist"));
}

#[test]
fn parses_research_promote_with_acceptance_metadata() {
let cli = Cli::parse_from([
Expand Down
Loading