Skip to main content

memnite_ingest/
brain.rs

1use std::fs;
2use std::path::Path;
3
4use memnite_core::Scope;
5
6use crate::error::IngestError;
7use crate::parse::split_h2_sections;
8use crate::source::{IngestSource, IngestedMemory};
9
10/// Adapter for guildgate's Repo Brain. Reads `UBIQUITOUS_LANGUAGE.md` (one memory
11/// per glossary term — the `## ` sections AFTER a `## Terms` heading) plus
12/// `docs/wiki/ai/CONTRACTS.md` and `CONSTRAINTS.md` (one whole-document memory
13/// each). Knows the FORMAT (a data contract), never guildgate's code. Missing
14/// files are skipped.
15pub struct RepoBrainSource;
16
17impl IngestSource for RepoBrainSource {
18    fn name(&self) -> &str {
19        "brain"
20    }
21
22    fn collect(&self, root: &Path) -> Result<Vec<IngestedMemory>, IngestError> {
23        let mut out = Vec::new();
24
25        let ul_path = root.join("UBIQUITOUS_LANGUAGE.md");
26        if ul_path.exists() {
27            let content = fs::read_to_string(&ul_path)?;
28            let sections = split_h2_sections(&content);
29            // Terms are the sections after the `## Terms` heading. Without that
30            // marker the file isn't in the expected glossary layout -> no terms.
31            if let Some(start) = sections
32                .iter()
33                .position(|(h, _)| h.eq_ignore_ascii_case("Terms"))
34            {
35                for (heading, body) in sections.into_iter().skip(start + 1) {
36                    out.push(IngestedMemory {
37                        source_key: format!("ubiquitous-language::{heading}"),
38                        title: heading,
39                        body,
40                        mem_type: "term".to_string(),
41                        topic_key: Some("ubiquitous-language".to_string()),
42                        scope: Scope::Repo,
43                    });
44                }
45            }
46        }
47
48        for (rel, title, mem_type, topic) in [
49            (
50                "docs/wiki/ai/CONTRACTS.md",
51                "CONTRACTS",
52                "contract",
53                "contracts",
54            ),
55            (
56                "docs/wiki/ai/CONSTRAINTS.md",
57                "CONSTRAINTS",
58                "constraint",
59                "constraints",
60            ),
61        ] {
62            let path = root.join(rel);
63            if path.exists() {
64                let content = fs::read_to_string(&path)?;
65                out.push(IngestedMemory {
66                    source_key: topic.to_string(),
67                    title: title.to_string(),
68                    body: content.trim().to_string(),
69                    mem_type: mem_type.to_string(),
70                    topic_key: Some(topic.to_string()),
71                    scope: Scope::Repo,
72                });
73            }
74        }
75
76        Ok(out)
77    }
78}