Skip to main content

apm/cmd/
verify.rs

1use anyhow::Result;
2use apm_core::config::{CompletionStrategy, Config};
3use std::collections::HashSet;
4use std::path::Path;
5use crate::ctx::CmdContext;
6
7pub fn run(root: &Path, fix: bool, no_aggressive: bool) -> Result<()> {
8    let ctx = CmdContext::load(root, no_aggressive)?;
9
10    let merged = apm_core::git::merged_into_main(root, &ctx.config.project.default_branch).unwrap_or_default();
11    let merged_set: HashSet<String> = merged.into_iter().collect();
12
13    let issues = apm_core::verify::verify_tickets(root, &ctx.config, &ctx.tickets, &merged_set);
14
15    // Report completion strategies configured on transitions.
16    for state in &ctx.config.workflow.states {
17        for tr in &state.transitions {
18            let label = match &tr.completion {
19                CompletionStrategy::Pr => "pr",
20                CompletionStrategy::Merge => "merge",
21                CompletionStrategy::Pull => "pull",
22                CompletionStrategy::PrOrEpicMerge => "pr_or_epic_merge",
23                CompletionStrategy::None => continue,
24            };
25            println!("completion: {} → {} = {label}", state.id, tr.to);
26        }
27    }
28
29    if ctx.config.logging.enabled {
30        let log_path = apm_core::logger::resolve_log_path(
31            &ctx.config.project.name,
32            ctx.config.logging.file.as_deref(),
33        );
34        println!("logging: {}", log_path.display());
35    }
36
37    if issues.is_empty() {
38        println!("verify: no issues found");
39        return Ok(());
40    }
41
42    for issue in &issues {
43        println!("{issue}");
44    }
45
46    if fix {
47        let merged_refs: HashSet<&str> = merged_set.iter().map(|s| s.as_str()).collect();
48        apply_fixes(root, &ctx.config, &ctx.tickets, &merged_refs)?;
49    }
50
51    std::process::exit(1);
52}
53
54fn apply_fixes(
55    root: &Path,
56    config: &Config,
57    tickets: &[apm_core::ticket::Ticket],
58    merged_set: &HashSet<&str>,
59) -> Result<()> {
60    for t in tickets {
61        let fm = &t.frontmatter;
62        let Some(branch) = &fm.branch else { continue };
63        if (fm.state == "in_progress" || fm.state == "implemented")
64            && merged_set.contains(branch.as_str())
65        {
66            let id = fm.id.clone();
67            let old_state = fm.state.clone();
68            match apm_core::ticket::close(root, config, &id, None, "verify --fix", false) {
69                Ok(msgs) => {
70                    for msg in &msgs {
71                        println!("{msg}");
72                    }
73                    println!("  fixed {id}: {old_state} → closed");
74                }
75                Err(e) => eprintln!("  warning: could not fix {id}: {e:#}"),
76            }
77        }
78    }
79    Ok(())
80}