forge-pilot 0.1.0

Closed-loop orchestrator over runtime advisories, kernel oracles, and canonical Forge export/import lanes
Documentation
use crate::bootstrap::types::{SourceFileRecord, SymbolRecord};

pub(crate) fn extract(file: &SourceFileRecord) -> Vec<SymbolRecord> {
    let mut symbols = Vec::new();
    for (index, line) in file.content.lines().enumerate() {
        let trimmed = strip_comment(line).trim();
        if trimmed.is_empty() {
            continue;
        }
        if let Some(name) = capture_name(trimmed, &["async def ", "def "], '(') {
            symbols.push(symbol(file, index + 1, name, "function", Some(trimmed)));
            continue;
        }
        if let Some(name) = capture_name(trimmed, &["class "], '(')
            .or_else(|| capture_name(trimmed, &["class "], ':'))
        {
            symbols.push(symbol(file, index + 1, name, "class", Some(trimmed)));
        }
    }
    symbols
}

fn strip_comment(line: &str) -> &str {
    line.split('#').next().unwrap_or(line)
}

fn capture_name<'a>(line: &'a str, prefixes: &[&str], terminator: char) -> Option<&'a str> {
    let rest = prefixes
        .iter()
        .find_map(|prefix| line.strip_prefix(prefix))?
        .trim_start();
    let name = rest
        .split([terminator, '<', ' ', ':'])
        .next()
        .unwrap_or_default()
        .trim();
    (!name.is_empty()).then_some(name)
}

fn symbol(
    file: &SourceFileRecord,
    line: usize,
    name: &str,
    kind: &str,
    signature: Option<&str>,
) -> SymbolRecord {
    SymbolRecord {
        symbol_id: format!(
            "workspace-source-symbol:{}",
            crate::bootstrap::manifest::digest_text(&format!(
                "{}:{}:{}:{}:{}",
                file.relative_path,
                file.content_digest.hex(),
                line,
                kind,
                name
            ))
        ),
        name: name.to_string(),
        kind: kind.to_string(),
        language: file.language.clone(),
        line_start: line,
        line_end: line,
        signature: signature.map(ToString::to_string),
        parent_chunk_id: None,
    }
}