Skip to main content

rac_engine/
improve.rs

1//! Artifact improvement (`decided.services.improve`): deterministic,
2//! schema-driven guidance. Advisory and read-only — reports missing
3//! required/recommended sections with schema-defined guidance questions.
4
5use crate::classify::{classify, missing_sections};
6use crate::parse::Artifact;
7use crate::spec::{spec_for, ArtifactSpec};
8
9/// `supports_improve(spec)` — every expected section defines guidance.
10/// (All five current specs pass, so the unsupported branch is dead in
11/// practice; ported for fidelity.)
12pub fn supports_improve(spec: &ArtifactSpec) -> bool {
13    spec.expected()
14        .iter()
15        .all(|section| spec.guidance.iter().any(|(k, _)| k == section))
16}
17
18/// Typed improvement analysis (`ImprovementResult`). Section names are
19/// stored normalized; the renderers format them.
20pub struct ImprovementResult {
21    /// Classified artifact type, or `"unknown"`.
22    pub artifact_type: String,
23    pub missing_required: Vec<String>,
24    pub missing_recommended: Vec<String>,
25    /// Schema guidance for the missing sections: `{section -> questions}`,
26    /// required-first then recommended, only sections that HAVE guidance.
27    pub guidance: Vec<(String, Vec<String>)>,
28    /// Whether `improve` produces suggestions for this type.
29    pub supported: bool,
30}
31
32/// `improve_product(product)` — analyze and return improvement guidance.
33pub fn improve_product(artifact: &Artifact) -> ImprovementResult {
34    let artifact_type = classify(artifact).artifact_type;
35    let spec = spec_for(&artifact_type);
36    let Some(spec) = spec.filter(|s| supports_improve(s)) else {
37        // Unknown, or a known type whose schema lacks complete guidance.
38        return ImprovementResult {
39            artifact_type,
40            missing_required: Vec::new(),
41            missing_recommended: Vec::new(),
42            guidance: Vec::new(),
43            supported: false,
44        };
45    };
46    let (missing_required, missing_recommended) = missing_sections(artifact, spec);
47    let mut guidance: Vec<(String, Vec<String>)> = Vec::new();
48    for s in missing_required.iter().chain(missing_recommended.iter()) {
49        // `if spec.guidance.get(s)` — only truthy (non-empty) guidance lists.
50        if let Some((_, g)) = spec.guidance.iter().find(|(k, _)| k == s) {
51            if !g.is_empty() {
52                guidance.push((s.clone(), g.clone()));
53            }
54        }
55    }
56    ImprovementResult {
57        artifact_type,
58        missing_required,
59        missing_recommended,
60        guidance,
61        supported: true,
62    }
63}