opensymphony 1.8.0

A Rust implementation of the OpenAI Symphony orchestration design
Documentation
pub fn plan_docs_sync(
    config: &MemoryConfig,
    selection: &IssueSelection,
    write: bool,
    with_diagrams: bool,
) -> Result<DocsSyncPlan, MemoryError> {
    let selected = select_indexed_issues_for_docs(config, selection)?;
    if selected.is_empty() {
        return Err(MemoryError::InvalidInput(
            "no captured issues selected for docs sync".to_string(),
        ));
    }
    let all_selected_issue_keys = selected
        .iter()
        .map(|issue| issue.issue_key.clone())
        .collect::<BTreeSet<_>>();

    let mut by_area: BTreeMap<String, (AreaConfig, Vec<IndexedIssue>)> = BTreeMap::new();
    let mut unmapped_areas = BTreeSet::new();
    for issue in selected {
        for area in issue.areas() {
            if selection
                .area
                .as_ref()
                .is_some_and(|selected_area| slugify(selected_area) != area)
            {
                continue;
            }
            if let Some(area_config) = config.areas.get(&area) {
                if area_config.status != AreaStatus::Stable
                    || area_config.confidence < config.confidence_threshold
                {
                    unmapped_areas.insert(area);
                    continue;
                }
                by_area
                    .entry(area)
                    .or_insert_with(|| (area_config.clone(), Vec::new()))
                    .1
                    .push(issue.clone());
            } else {
                unmapped_areas.insert(area);
            }
        }
    }
    let mut targets = Vec::new();
    let mut warnings = Vec::new();
    if by_area.is_empty() {
        warnings.push(missing_docs_area_mapping_message(config, unmapped_areas));
    }
    for (_area_slug, (area, issues)) in by_area {
        let before = if area.docs_target.exists() {
            Some(read_to_string(&area.docs_target)?)
        } else {
            None
        };
        let after = render_topic_doc(config, &area, &issues, before.as_deref(), with_diagrams);
        if area.visibility == MemoryVisibility::Public
            && config.docs.deny_private_links
            && contains_private_memory_link(&after)
        {
            warnings.push(format!(
                "{} would contain private memory links",
                display_path(&config.repo_root, &area.docs_target)
            ));
        }
        let diff = render_diff_stat(before.as_deref().unwrap_or(""), &after, &area.docs_target);
        targets.push(DocsTargetPlan {
            area: area.slug,
            title: area.title,
            path: area.docs_target,
            visibility: area.visibility,
            create: before.is_none(),
            before,
            after,
            diff,
            issue_keys: issues.into_iter().map(|issue| issue.issue_key).collect(),
        });
    }

    let mut selected_issue_keys = targets
        .iter()
        .flat_map(|target| target.issue_keys.clone())
        .collect::<BTreeSet<_>>()
        .into_iter()
        .collect::<Vec<_>>();
    if selected_issue_keys.is_empty() {
        selected_issue_keys = all_selected_issue_keys.into_iter().collect();
    }

    Ok(DocsSyncPlan {
        write,
        selected_issue_keys,
        targets,
        warnings,
    })
}

fn missing_docs_area_mapping_message(
    config: &MemoryConfig,
    unmapped_areas: BTreeSet<String>,
) -> String {
    if config.areas.is_empty() {
        return "No stable learned docs area found. Run opensymphony memory init or capture issues with stronger Linear/PR evidence.".to_string();
    }

    if unmapped_areas.is_empty() {
        return "Selected issues have no stable docs areas yet; capture kept their memory private until confidence improves.".to_string();
    }

    let areas = unmapped_areas.into_iter().collect::<Vec<_>>().join(", ");
    format!(
        "selected issues only reference candidate or unmapped docs areas ({areas}); capture kept them private until confidence improves."
    )
}

pub fn write_docs_sync_plan(
    config: &MemoryConfig,
    plan: &DocsSyncPlan,
) -> Result<Vec<PathBuf>, MemoryError> {
    let mut written = Vec::new();
    for target in &plan.targets {
        ensure_repo_contained(&config.repo_root, &target.path)?;
        write_file(&target.path, &target.after)?;
        written.push(target.path.clone());
    }
    mark_docs_synced(config, plan)?;
    Ok(written)
}