git_adr/cli/
supersede.rs

1//! Create a superseding ADR.
2
3use anyhow::Result;
4use chrono::Utc;
5use clap::Args as ClapArgs;
6use colored::Colorize;
7
8use crate::core::{Adr, AdrStatus, ConfigManager, FlexibleDate, Git, NotesManager, TemplateEngine};
9
10/// Arguments for the supersede command.
11#[derive(ClapArgs, Debug)]
12pub struct Args {
13    /// ADR ID to supersede.
14    pub adr_id: String,
15
16    /// Title for the new ADR.
17    pub title: String,
18
19    /// Template format for new ADR.
20    #[arg(long)]
21    pub template: Option<String>,
22}
23
24/// Run the supersede command.
25///
26/// # Errors
27///
28/// Returns an error if supersession fails.
29pub fn run(args: Args) -> Result<()> {
30    let git = Git::new();
31    git.check_repository()?;
32
33    let config = ConfigManager::new(git.clone()).load()?;
34    let notes = NotesManager::new(git.clone(), config.clone());
35
36    // Find the ADR to supersede
37    let adrs = notes.list()?;
38    let mut old_adr = adrs
39        .into_iter()
40        .find(|a| a.id == args.adr_id || a.id.contains(&args.adr_id))
41        .ok_or_else(|| anyhow::anyhow!("ADR not found: {}", args.adr_id))?;
42
43    eprintln!(
44        "{} Superseding ADR {} with: {}",
45        "→".blue(),
46        old_adr.id.cyan(),
47        args.title.yellow()
48    );
49
50    // Generate new ADR ID
51    let next_num = notes.next_number()?;
52    let new_adr_id = notes.format_id(next_num);
53
54    // Determine template format
55    let format = args.template.as_deref().unwrap_or(&config.format);
56
57    // Create new ADR
58    let mut new_adr = Adr::new(new_adr_id.clone(), args.title.clone());
59    new_adr.commit = git.head()?;
60    new_adr.frontmatter.status = AdrStatus::Proposed;
61    new_adr.frontmatter.date = Some(FlexibleDate(Utc::now()));
62    new_adr.frontmatter.format = Some(format.to_string());
63
64    // Inherit tags from old ADR
65    new_adr
66        .frontmatter
67        .tags
68        .clone_from(&old_adr.frontmatter.tags);
69
70    // Add supersedes link
71    new_adr.frontmatter.supersedes = Some(old_adr.id.clone());
72
73    // Render template for body
74    let template_engine = TemplateEngine::new();
75    let mut context = std::collections::HashMap::new();
76    context.insert("title".to_string(), new_adr.frontmatter.title.clone());
77    context.insert("status".to_string(), new_adr.frontmatter.status.to_string());
78    let body = template_engine.render(format, &context)?;
79    new_adr.body = body;
80
81    // Save new ADR
82    notes.create(&new_adr)?;
83
84    // Update old ADR status to superseded
85    old_adr.frontmatter.status = AdrStatus::Superseded;
86    old_adr.frontmatter.superseded_by = Some(new_adr_id.clone());
87    notes.update(&old_adr)?;
88
89    eprintln!("{} Created new ADR: {}", "✓".green(), new_adr_id.cyan());
90    eprintln!(
91        "{} Updated {} status to superseded",
92        "✓".green(),
93        old_adr.id.cyan()
94    );
95
96    Ok(())
97}