pub fn estimate_tokens(text: &str) -> usize {
text.chars().count() / 4
}
pub const MIN_TRANSPORT_CHUNK_BUDGET: usize = 4096;
pub fn floor_chunk_budget(requested: usize) -> usize {
requested.max(MIN_TRANSPORT_CHUNK_BUDGET)
}
pub fn chunk_markdown(markdown: &str, budget: usize) -> Option<Vec<String>> {
let char_budget = budget * 4;
if markdown.len() <= char_budget {
return None;
}
let mut chunks = Vec::new();
let mut remaining = markdown;
while !remaining.is_empty() {
if remaining.len() <= char_budget {
chunks.push(remaining.to_string());
break;
}
let safe_budget = remaining.floor_char_boundary(char_budget);
if safe_budget == 0 {
chunks.push(remaining.to_string());
break;
}
let (split_at, advance) = match remaining[..safe_budget].rfind('\n').filter(|&i| i > 0) {
Some(i) => (i, i + 1),
None => (safe_budget, safe_budget),
};
chunks.push(remaining[..split_at].to_string());
remaining = &remaining[advance..];
}
Some(chunks)
}
pub fn apply_chunking(
markdown: &str,
budget: usize,
chunk: Option<usize>,
extra_fm: &[(&str, &str)],
) -> Result<String, String> {
let chunks_opt = chunk_markdown(markdown, budget);
let total = chunks_opt.as_ref().map(|c| c.len()).unwrap_or(1);
let idx = chunk.unwrap_or(1).saturating_sub(1);
if idx >= total {
return Err(format!(
"Chunk {} does not exist. Content has {} chunk{s}.",
idx + 1,
total,
s = if total == 1 { "" } else { "s" },
));
}
if chunks_opt.is_none() {
return Ok(inject_chunk_frontmatter(markdown, 1, 1, false));
}
let chunks = chunks_opt.unwrap();
let is_last = idx == total - 1;
let original_fm = extract_frontmatter_lines(markdown);
let merged_fm = merge_chunk_frontmatter(&original_fm, extra_fm, idx + 1, total, !is_last);
let result = if idx == 0 {
if let Some(end) = find_frontmatter_end(&chunks[idx]) {
format!("---\n{merged_fm}\n---{}", &chunks[idx][end..])
} else {
format!("---\n{merged_fm}\n---\n\n{}", chunks[idx])
}
} else {
format!("---\n{merged_fm}\n---\n\n{}", chunks[idx])
};
Ok(result)
}
fn extract_frontmatter_lines(markdown: &str) -> Vec<(String, String)> {
let Some(end) = find_frontmatter_end(markdown) else {
return Vec::new();
};
let inner_end = end - 4;
let inner = &markdown[4..inner_end];
inner
.lines()
.filter_map(|line| {
let trimmed = line.trim_end();
if trimmed.is_empty() {
return None;
}
let colon = trimmed.find(':')?;
let key = trimmed[..colon].trim().to_string();
let value = trimmed[colon + 1..].trim_start().to_string();
if key.is_empty() {
None
} else {
Some((key, value))
}
})
.collect()
}
fn merge_chunk_frontmatter(
original: &[(String, String)],
extra_fm: &[(&str, &str)],
idx: usize,
total: usize,
truncated: bool,
) -> String {
use indexmap::IndexMap;
const CHUNK_WALK_KEYS: &[&str] = &["_truncated", "_chunk", "_total_chunks"];
let mut keyed: IndexMap<String, String> = IndexMap::new();
for (k, v) in original {
if CHUNK_WALK_KEYS.contains(&k.as_str()) {
continue; }
keyed.insert(k.clone(), v.clone());
}
for (k, v) in extra_fm {
if CHUNK_WALK_KEYS.contains(k) {
continue;
}
keyed.insert((*k).to_string(), (*v).to_string());
}
if truncated {
keyed.insert("_truncated".to_string(), "true".to_string());
}
keyed.insert("_chunk".to_string(), format!("{idx} of {total}"));
keyed.insert("_total_chunks".to_string(), total.to_string());
keyed
.iter()
.map(|(k, v)| format!("{k}: {v}"))
.collect::<Vec<_>>()
.join("\n")
}
fn inject_chunk_frontmatter(markdown: &str, idx: usize, total: usize, truncated: bool) -> String {
let chunk_line = format!("_chunk: {idx} of {total}");
let total_line = format!("_total_chunks: {total}");
let truncated_line = if truncated { "_truncated: true\n" } else { "" };
match find_frontmatter_end(markdown) {
Some(end) => {
let inner_end = end - 4;
let inner = markdown[4..inner_end].trim_end_matches('\n');
let separator = if inner.is_empty() { "" } else { "\n" };
format!(
"---\n{inner}{separator}{truncated_line}{chunk_line}\n{total_line}\n---{}",
&markdown[end..]
)
}
None => format!("---\n{truncated_line}{chunk_line}\n{total_line}\n---\n\n{markdown}"),
}
}
fn find_frontmatter_end(text: &str) -> Option<usize> {
if !text.starts_with("---\n") {
return None;
}
text[4..].find("\n---").map(|pos| pos + 4 + 4) }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn estimate_tokens_basic() {
assert_eq!(estimate_tokens("hello world!"), 3); }
#[test]
fn floor_chunk_budget_raises_tiny_budgets_only() {
assert_eq!(floor_chunk_budget(5), MIN_TRANSPORT_CHUNK_BUDGET);
assert_eq!(floor_chunk_budget(0), MIN_TRANSPORT_CHUNK_BUDGET);
assert_eq!(floor_chunk_budget(25_000), 25_000);
let small_body = "line one\nline two\nline three\n";
assert!(chunk_markdown(small_body, floor_chunk_budget(5)).is_none());
assert!(chunk_markdown(small_body, 5).unwrap().len() > 1);
}
#[test]
fn chunk_small_content_returns_none() {
let text = "short";
assert!(chunk_markdown(text, 100).is_none());
}
#[test]
fn chunk_splits_at_newline_boundaries() {
let text = "line1\nline2\nline3\nline4\nline5\n";
let chunks = chunk_markdown(text, 2).unwrap();
assert!(chunks.len() > 1);
for chunk in &chunks[..chunks.len() - 1] {
assert!(chunk.ends_with('\n') || !chunk.contains('\n'));
}
}
#[test]
fn apply_chunking_no_split_needed_injects_chunk_metadata() {
let md = "---\n_hash: abc\n---\n\n# Title\n\nContent";
let result = apply_chunking(md, 10000, None, &[]).unwrap();
assert!(
result.contains("_hash: abc"),
"preserves existing frontmatter key"
);
assert!(result.contains("_chunk: 1 of 1"), "got: {result}");
assert!(result.contains("_total_chunks: 1"), "got: {result}");
assert!(result.ends_with("# Title\n\nContent"), "preserves body");
}
#[test]
fn apply_chunking_invalid_chunk_returns_error() {
let md = "a\nb\n".repeat(100);
let result = apply_chunking(&md, 1, Some(999), &[]);
assert!(result.is_err());
}
#[test]
fn apply_chunking_out_of_range_errors_even_when_no_split_needed() {
let md = "---\n_hash: x\n---\n\n# Small\n";
let result = apply_chunking(md, 10000, Some(99), &[]);
assert!(result.is_err(), "out-of-range request must fail");
}
#[test]
fn apply_chunking_no_frontmatter_prepends_one() {
let md = "# Bare\n\nNo frontmatter here.";
let result = apply_chunking(md, 10000, None, &[]).unwrap();
assert!(result.starts_with("---\n_chunk: 1 of 1\n_total_chunks: 1\n---"));
assert!(result.contains("# Bare"));
}
#[test]
fn apply_chunking_preserves_entity_frontmatter_on_chunk_1() {
let mut md = String::from(
"---\n\
_hash: abc123\n\
type: spec\n\
level: M0\n\
stability: stable\n\
created_date: 2026-01-01\n\
last_modified: 2026-05-17\n\
_tokens: 9999\n\
---\n\n\
# Title\n\n\
",
);
for i in 0..200 {
md.push_str(&format!(
"body line {i} with enough content to span chunks\n"
));
}
let result = apply_chunking(
&md,
100,
Some(1),
&[("_hash", "fresh-hash"), ("_mem_schema", "default@1.0.0")],
)
.unwrap();
for key in [
"type:",
"level:",
"stability:",
"created_date:",
"last_modified:",
] {
assert!(
result.contains(key),
"chunk 1 must carry the entity-level `{key}` frontmatter key — got:\n{result}",
);
}
assert!(
result.contains("_hash: fresh-hash"),
"extra_fm must override the original frontmatter's `_hash`",
);
assert!(
result.contains("_mem_schema: default@1.0.0"),
"extra_fm key must be present",
);
assert!(result.contains("_truncated: true"));
assert!(result.contains("_chunk: 1 of "));
}
#[test]
fn apply_chunking_preserves_entity_frontmatter_on_later_chunks() {
let mut md = String::from(
"---\n\
_hash: abc123\n\
type: memo\n\
level: M1\n\
created_date: 2026-01-01\n\
_tokens_unfiltered_body: 5000\n\
---\n\n\
# Title\n\n\
",
);
for i in 0..300 {
md.push_str(&format!(
"body line {i}: long enough content for spread chunking\n"
));
}
let chunk_1 = apply_chunking(&md, 100, Some(1), &[("_hash", "h")]).unwrap();
let total_chunks: usize = chunk_1
.lines()
.find_map(|l| l.strip_prefix("_total_chunks: "))
.and_then(|s| s.parse().ok())
.unwrap_or_else(|| panic!("chunk 1 must declare _total_chunks: {chunk_1}"));
assert!(total_chunks >= 3, "test fixture must produce ≥3 chunks");
for chunk_idx in 2..=total_chunks {
let chunk = apply_chunking(&md, 100, Some(chunk_idx), &[("_hash", "h")]).unwrap();
for key in ["type: memo", "level: M1", "created_date: 2026-01-01"] {
assert!(
chunk.contains(key),
"chunk {chunk_idx} must carry `{key}` in its frontmatter — got:\n{chunk}",
);
}
}
}
#[test]
fn apply_chunking_caller_supplied_wins_on_collision() {
let mut md = String::from(
"---\n\
_hash: stale-from-prior-write\n\
type: spec\n\
---\n\n",
);
for i in 0..200 {
md.push_str(&format!("line {i}: filler to force multi-chunk emission\n"));
}
let chunk_1 =
apply_chunking(&md, 100, Some(1), &[("_hash", "post-mutation-hash")]).unwrap();
assert!(chunk_1.contains("_hash: post-mutation-hash"));
assert!(
!chunk_1.contains("_hash: stale-from-prior-write"),
"stale hash must not survive the merge",
);
}
#[test]
fn chunk_tiny_budgets_em_dash() {
for body in ["—text", "te—xt", "text—", "—a—b—c—", " — — —"] {
for budget in 0..=2 {
let _ = chunk_markdown(body, budget); }
}
}
#[test]
fn chunk_tiny_budgets_cjk() {
for body in ["日本語", "日本語テスト", "abc日本語def", "日a本b語c"] {
for budget in 0..=4 {
let _ = chunk_markdown(body, budget);
}
}
}
#[test]
fn chunk_tiny_budgets_emoji_vs() {
let heart_vs = "\u{2764}\u{FE0F}";
let body = format!("{heart_vs}{heart_vs}{heart_vs}text{heart_vs}{heart_vs}");
for budget in 0..=5 {
let _ = chunk_markdown(&body, budget);
}
}
#[test]
fn chunk_markdown_budget_zero_emits_single_chunk() {
let chunks = chunk_markdown("any non-trivial body", 0).expect("non-empty body chunks");
assert_eq!(chunks.len(), 1);
assert_eq!(chunks[0], "any non-trivial body");
}
#[test]
fn apply_chunking_tiny_budgets_with_em_dash() {
let md = "---\n_hash: x\n---\n\n# Title — with em-dash\n\nMore body — even more.";
for budget in 0..=2 {
let result = apply_chunking(md, budget, None, &[]);
assert!(
result.is_ok(),
"budget {budget} must not panic or error: {result:?}"
);
}
}
#[test]
fn chunk_markdown_byte_identical_for_budget_ten_plus() {
let body = "line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\n".repeat(20);
for budget in [10, 25, 50, 100, 250, 1000] {
let chunks = chunk_markdown(&body, budget).unwrap_or_else(|| vec![body.clone()]);
let rejoined = chunks.join("\n");
assert!(
rejoined == body || rejoined == body.trim_end_matches('\n'),
"budget={budget}: chunk roundtrip must equal source"
);
}
}
#[test]
fn apply_chunking_cross_chunk_frontmatter_consistency() {
let mut md = String::from(
"---\n\
_hash: abc\n\
type: decision\n\
level: M2\n\
stability: stable\n\
---\n\n",
);
for i in 0..300 {
md.push_str(&format!("line {i}: filler\n"));
}
let chunk_1 = apply_chunking(&md, 100, Some(1), &[]).unwrap();
let chunk_2 = apply_chunking(&md, 100, Some(2), &[]).unwrap();
for key in ["type: decision", "level: M2", "stability: stable"] {
assert!(chunk_1.contains(key), "chunk_1 missing `{key}`");
assert!(chunk_2.contains(key), "chunk_2 missing `{key}`");
}
}
}