Shared memory and context tools for agentic work.
Code Rooms
use crate::{IngestAdapter, IngestStats};
use m1nd_core::error::M1ndResult;
use m1nd_core::graph::{Graph, NodeProvenanceInput};
use m1nd_core::types::{EdgeDirection, FiniteF32, NodeType};
use quick_xml::events::{BytesRef, Event};
use quick_xml::Reader;
/// Resolve a general-entity reference into captured text. quick-xml 0.38+
/// reports every `&name;`/`&#N;` in text as a separate `Event::GeneralRef`;
/// dropping them silently corrupts extracted fields ("AT&T" -> "ATT").
pub(crate) fn append_general_ref(buf: &mut String, r: &BytesRef<'_>) {
if let Ok(Some(ch)) = r.resolve_char_ref() {
buf.push(ch);
return;
}
match r.decode() {
Ok(name) => match name.as_ref() {
"amp" => buf.push('&'),
"lt" => buf.push('<'),
"gt" => buf.push('>'),
"apos" => buf.push('\''),
"quot" => buf.push('"'),
other => {
// Unknown entity: keep it verbatim rather than dropping content.
buf.push('&');
buf.push_str(other);
buf.push(';');
},
Err(_) => {
buf.push_str(&String::from_utf8_lossy(r));
use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use walkdir::WalkDir;
// ─── Extracted Record ──────────────────────────────────────────────
#[derive(Default, Debug)]
struct RfcRecord {
number: String, // 9110
title: String, // HTTP Semantics
authors: Vec<String>, // Roy T. Fielding, ...
year: String,
category: String, // std, info, bcp, exp
keywords: Vec<String>,
abstract_text: String,
obsoletes: Vec<String>, // RFC numbers this obsoletes
updates: Vec<String>, // RFC numbers this updates
references: Vec<RfcRef>,
#[derive(Default, Debug, Clone)]
struct RfcRef {
rfc_number: String, // if seriesInfo name=RFC
doi: String,
title: String,
target: String, // URL
impl RfcRecord {
fn external_id(&self) -> String {
format!("rfc::{}", self.number)
impl RfcRef {
fn best_id(&self) -> Option<String> {
if !self.rfc_number.is_empty() {
Some(format!("rfc::{}", self.rfc_number))
} else if !self.doi.is_empty() {
Some(format!("doi::{}", self.doi))
} else {
None
// ─── Parser ────────────────────────────────────────────────────────
fn parse_rfc_xml(xml: &str) -> Vec<RfcRecord> {
let mut reader = Reader::from_str(xml);
let mut buf = Vec::new();
let mut path: Vec<String> = Vec::new();
let mut text_buf = String::new();
let mut records: Vec<RfcRecord> = Vec::new();
let mut current: Option<RfcRecord> = None;
let mut current_ref: Option<RfcRef> = None;
let mut in_title = false;
let mut in_abstract = false;
let mut in_keyword = false;
let mut in_ref_title = false;
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
let name = String::from_utf8_lossy(e.name().as_ref()).to_string();
path.push(name.clone());
match name.as_str() {
"rfc" => {
let mut rec = RfcRecord::default();
for attr in e.attributes().filter_map(|a| a.ok()) {
let key = String::from_utf8_lossy(attr.key.as_ref()).to_string();
let val = String::from_utf8_lossy(&attr.value).to_string();
match key.as_str() {
"number" => rec.number = val,
"category" => rec.category = val,
"obsoletes" => {
rec.obsoletes = val
.split(", ")
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
"updates" => {
rec.updates = val
_ => {}
current = Some(rec);
"title"
if path.iter().any(|p| p == "front")
&& !path.iter().any(|p| p == "reference") =>
{
in_title = true;
"title" if path.iter().any(|p| p == "reference") => {
in_ref_title = true;
"abstract" => in_abstract = true,
"keyword" => in_keyword = true,
"date"
if let Some(ref mut rec) = current {
if String::from_utf8_lossy(attr.key.as_ref()) == "year" {
rec.year = String::from_utf8_lossy(&attr.value).to_string();
"author"
if String::from_utf8_lossy(attr.key.as_ref()) == "fullname" {
let name = String::from_utf8_lossy(&attr.value).to_string();
if !name.is_empty() {
rec.authors.push(name);
"reference" => {
let mut rf = RfcRef::default();
if String::from_utf8_lossy(attr.key.as_ref()) == "target" {
rf.target = String::from_utf8_lossy(&attr.value).to_string();
current_ref = Some(rf);
"seriesInfo" if path.iter().any(|p| p == "reference") => {
if let Some(ref mut rf) = current_ref {
let mut sname = String::new();
let mut sval = String::new();
"name" => sname = val,
"value" => sval = val,
match sname.as_str() {
"RFC" => rf.rfc_number = sval,
"DOI" => rf.doi = sval,
Ok(Event::End(e)) => {
if let Some(rec) = current.take() {
if !rec.number.is_empty() {
records.push(rec);
"title" if in_title => {
in_title = false;
if rec.title.is_empty() {
rec.title = text_buf.trim().to_string();
text_buf.clear();
"title" if in_ref_title => {
in_ref_title = false;
rf.title = text_buf.trim().to_string();
"abstract" => {
in_abstract = false;
if !text_buf.is_empty() {
rec.abstract_text = text_buf.trim().to_string();
"keyword" => {
in_keyword = false;
rec.keywords.push(text_buf.trim().to_string());
if let Some(rf) = current_ref.take() {
if rf.best_id().is_some() {
rec.references.push(rf);
path.pop();
Ok(Event::Text(e)) if in_title || in_abstract || in_keyword || in_ref_title => {
if let Ok(t) = e.decode() {
text_buf.push_str(&t);
Ok(Event::GeneralRef(e)) if in_title || in_abstract || in_keyword || in_ref_title => {
append_general_ref(&mut text_buf, &e);
Ok(Event::Empty(e)) => {
Ok(Event::Eof) => break,
Err(_) => break,
buf.clear();
records
// ─── Adapter ───────────────────────────────────────────────────────
pub struct RfcAdapter {
namespace: String,
impl RfcAdapter {
pub fn new(namespace: Option<String>) -> Self {
let namespace = namespace
.unwrap_or_else(|| "rfc".to_string())
.trim()
.to_lowercase();
let namespace = if namespace.is_empty() {
"rfc".to_string()
namespace
};
Self { namespace }
fn accepted_extension(path: &Path) -> bool {
matches!(
path.extension()
.and_then(|e| e.to_str())
.map(|e| e.to_ascii_lowercase()),
Some(ext) if ext == "xml"
)
fn collect_files(&self, root: &Path) -> Vec<PathBuf> {
if root.is_file() {
return if Self::accepted_extension(root) {
vec![root.to_path_buf()]
vec![]
WalkDir::new(root)
.into_iter()
.filter_map(Result::ok)
.filter(|e| e.file_type().is_file() && Self::accepted_extension(e.path()))
.map(|e| e.into_path())
.collect()
fn file_timestamp(path: &Path) -> f64 {
std::fs::metadata(path)
.ok()
.and_then(|m| m.modified().ok())
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
.map(|d| d.as_secs_f64())
.unwrap_or(0.0)
impl IngestAdapter for RfcAdapter {
fn domain(&self) -> &str {
"rfc"
fn ingest(&self, root: &Path) -> M1ndResult<(Graph, IngestStats)> {
let start = Instant::now();
let files = self.collect_files(root);
let mut stats = IngestStats {
files_scanned: files.len() as u64,
..Default::default()
let mut node_ids: HashSet<String> = HashSet::new();
let mut edge_keys: HashSet<(String, String, String)> = HashSet::new();
struct NodeRec {
id: String,
label: String,
ntype: NodeType,
tags: Vec<String>,
ts: f64,
src: String,
excerpt: Option<String>,
ns: String,
struct EdgeRec {
source: String,
target: String,
rel: String,
w: f32,
let mut nodes: Vec<NodeRec> = Vec::new();
let mut edges: Vec<EdgeRec> = Vec::new();
for path in &files {
let content = match std::fs::read_to_string(path) {
Ok(c) => c,
Err(_) => continue,
// Quick check: must contain <rfc
if !content.contains("<rfc") {
continue;
let rel = path
.strip_prefix(root)
.unwrap_or(path)
.to_string_lossy()
.replace('\\', "/");
let ts = Self::file_timestamp(path);
let records = parse_rfc_xml(&content);
for rec in records {
let ext_id = rec.external_id();
if !node_ids.insert(ext_id.clone()) {
let mut tags = vec![
"rfc".to_string(),
format!("rfc:category:{}", rec.category),
"rfc:state:published".to_string(),
format!("namespace:{}", self.namespace),
];
if !rec.year.is_empty() {
tags.push(format!("rfc:year:{}", rec.year));
for kw in &rec.keywords {
tags.push(format!("rfc:keyword:{}", kw.to_lowercase()));
let excerpt = if !rec.abstract_text.is_empty() {
Some(rec.abstract_text.chars().take(220).collect())
nodes.push(NodeRec {
id: ext_id.clone(),
label: format!("RFC {} — {}", rec.number, rec.title),
ntype: NodeType::File,
tags,
ts,
src: rel.clone(),
excerpt,
ns: self.namespace.clone(),
});
// References
for rf in &rec.references {
if let Some(target_id) = rf.best_id() {
let key = (ext_id.clone(), target_id.clone(), "references".to_string());
if edge_keys.insert(key) {
if node_ids.insert(target_id.clone()) {
let lbl = if !rf.title.is_empty() {
rf.title.clone()
target_id.clone()
id: target_id.clone(),
label: lbl,
ntype: NodeType::Reference,
tags: vec!["rfc".to_string(), "rfc:cited".to_string()],
excerpt: None,
edges.push(EdgeRec {
source: ext_id.clone(),
target: target_id,
rel: "references".to_string(),
w: 0.8,
// Obsoletes edges
for obs in &rec.obsoletes {
let target_id = format!("rfc::{}", obs);
let key = (ext_id.clone(), target_id.clone(), "obsoletes".to_string());
label: format!("RFC {}", obs),
tags: vec!["rfc".to_string(), "rfc:state:obsoleted".to_string()],
rel: "obsoletes".to_string(),
w: 1.0,
// Updates edges
for upd in &rec.updates {
let target_id = format!("rfc::{}", upd);
let key = (ext_id.clone(), target_id.clone(), "updates".to_string());
label: format!("RFC {}", upd),
tags: vec!["rfc".to_string()],
rel: "updates".to_string(),
w: 0.9,
// Author nodes
for author in &rec.authors {
let aid = format!("rfc::author::{}", author.to_lowercase().replace(' ', "_"));
if node_ids.insert(aid.clone()) {
id: aid.clone(),
label: author.clone(),
ntype: NodeType::Concept,
tags: vec!["rfc".to_string(), "rfc:author".to_string()],
let key = (ext_id.clone(), aid.clone(), "authored_by".to_string());
target: aid,
rel: "authored_by".to_string(),
stats.files_parsed += 1;
// Build graph
let mut graph = Graph::with_capacity(nodes.len(), edges.len());
for n in &nodes {
let tags_ref: Vec<&str> = n.tags.iter().map(String::as_str).collect();
if let Ok(nid) = graph.add_node(&n.id, &n.label, n.ntype, &tags_ref, n.ts, 0.5) {
graph.set_node_provenance(
nid,
NodeProvenanceInput {
source_path: Some(&n.src),
line_start: None,
line_end: None,
excerpt: n.excerpt.as_deref(),
namespace: Some(&n.ns),
canonical: true,
);
stats.nodes_created += 1;
for e in &edges {
if let (Some(s), Some(t)) = (graph.resolve_id(&e.source), graph.resolve_id(&e.target)) {
if graph
.add_edge(
s,
t,
&e.rel,
FiniteF32::new(e.w),
EdgeDirection::Forward,
false,
FiniteF32::new(0.7),
.is_ok()
stats.edges_created += 1;
if graph.num_nodes() > 0 {
graph.finalize()?;
stats.elapsed_ms = start.elapsed().as_secs_f64() * 1000.0;
Ok((graph, stats))
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolves_general_entity_refs_in_captured_text() {
let xml = r#"<?xml version="1.0"?>
<rfc number="1">
<front>
<title>AT&T <proxy> ≤ limit</title>
<abstract><t>p < 0.05 & q > 1</t></abstract>
</front>
</rfc>"#;
let records = parse_rfc_xml(xml);
assert_eq!(records.len(), 1);
assert_eq!(records[0].title, "AT&T <proxy> \u{2264} limit");
assert_eq!(records[0].abstract_text, "p < 0.05 & q > 1");
fn parses_rfc_xml() {
<rfc number="9110" category="std" obsoletes="7230, 7231" updates="3864">
<title>HTTP Semantics</title>
<author fullname="Roy T. Fielding"/>
<author fullname="Mark Nottingham"/>
<date year="2022" month="06"/>
<keyword>HTTP</keyword>
<keyword>protocol</keyword>
<abstract><t>The Hypertext Transfer Protocol is stateless.</t></abstract>
<back>
<references>
<reference target="https://www.rfc-editor.org/info/rfc9111">
<front><title>HTTP Caching</title></front>
<seriesInfo name="RFC" value="9111"/>
</reference>
<reference>
<front><title>Some DOI paper</title></front>
<seriesInfo name="DOI" value="10.1000/test"/>
</references>
</back>
let r = &records[0];
assert_eq!(r.number, "9110");
assert_eq!(r.title, "HTTP Semantics");
assert_eq!(r.authors.len(), 2);
assert_eq!(r.category, "std");
assert_eq!(r.obsoletes, vec!["7230", "7231"]);
assert_eq!(r.updates, vec!["3864"]);
assert_eq!(r.keywords.len(), 2);
assert_eq!(r.references.len(), 2);
assert_eq!(r.references[0].rfc_number, "9111");
assert_eq!(r.references[1].doi, "10.1000/test");
fn ingest_creates_graph() {
let adapter = RfcAdapter::new(None);
let dir = std::env::temp_dir().join("rfc-test-basic");
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(
dir.join("rfc.xml"),
r#"<?xml version="1.0"?>
<rfc number="7231" category="std">
<title>HTTP/1.1 Semantics</title>
<author fullname="Roy Fielding"/>
<date year="2014"/>
<reference><front><title>URI</title></front>
<seriesInfo name="RFC" value="3986"/>
</rfc>"#,
.unwrap();
let (graph, stats) = adapter.ingest(&dir).expect("ingest failed");
assert!(stats.nodes_created >= 3); // rfc + author + reference
assert!(stats.edges_created >= 2); // references + authored_by
assert!(graph.resolve_id("rfc::7231").is_some());
assert!(graph.resolve_id("rfc::3986").is_some());
assert!(graph.resolve_id("rfc::author::roy_fielding").is_some());
std::fs::remove_dir_all(&dir).ok();
fn ingest_real_rfc() {
let path = Path::new("/tmp/patent-test/rfc9110.xml");
if !path.exists() {
println!("SKIP: real RFC data not available");
let (graph, stats) = adapter.ingest(path).expect("ingest failed");
println!("=== Real RFC 9110 (HTTP Semantics, 1.2MB) ===");
println!(" nodes: {}", stats.nodes_created);
println!(" edges: {}", stats.edges_created);
println!(" elapsed: {:.2}ms", stats.elapsed_ms);
assert!(graph.resolve_id("rfc::9110").is_some());
assert!(graph.resolve_id("rfc::author::roy_t._fielding").is_some());
// Should reference RFC 9111 (HTTP Caching)
assert!(graph.resolve_id("rfc::9111").is_some());
assert!(
stats.nodes_created >= 50,
"expected many nodes from 84 references"
println!(" PASS ✓");