Skip to content
Closed
Changes from 1 commit
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
4adcf37
docs(spec): life-capture layer design
jwalinsshah Apr 22, 2026
36eb444
docs(spec): cut Slack from life-capture v1, defer to v1.1
jwalinsshah Apr 22, 2026
f166a7b
docs(plans): Track 1 ship pipeline + Life-Capture #1 foundation
jwalinsshah Apr 22, 2026
e5b8c5c
fix(install): resolver function + reachability retry + smoke test
jwalinsshah Apr 22, 2026
e160e58
docs(plans): incorporate Onyx insights — ACL on items, sync_state tab…
jwalinsshah Apr 22, 2026
d7f2284
docs(plans): drop ubuntu from CI matrix in Track 1; preserve resolver…
jwalinsshah Apr 22, 2026
78fb199
docs(plans): add F15-F17 — curated MEMORY.md/USER.md layer
jwalinsshah Apr 22, 2026
0cdf247
feat(life_capture): module skeleton + sqlite-vec dep
jwalinsshah Apr 22, 2026
1d45ffb
feat(life_capture): core types — Item, Source, Person, Query, Hit, In…
jwalinsshah Apr 22, 2026
da09f01
feat(life_capture): PII redaction (email, phone, SSN, CC)
jwalinsshah Apr 22, 2026
a938f90
feat(life_capture): strip quoted replies from email bodies
jwalinsshah Apr 22, 2026
24d8264
feat(life_capture): SQLite schema + migration loader (rusqlite)
jwalinsshah Apr 22, 2026
ae57f28
feat(life_capture): Embedder trait + HostedEmbedder (OpenAI-compatible)
jwalinsshah Apr 22, 2026
f0441b5
fix(life_capture): make country code prefix fully optional in PHONE r…
jwalinsshah Apr 22, 2026
37da5e1
feat(curated_memory): MemoryStore for MEMORY.md + USER.md (atomic wri…
jwalinsshah Apr 22, 2026
5f97a00
feat(life_capture): sqlite-vec extension loading + 1536d item_vectors…
jwalinsshah Apr 22, 2026
be8e6a7
feat(life_capture): IndexWriter — upsert items with (source, external…
jwalinsshah Apr 22, 2026
2b11731
feat(life_capture): IndexReader keyword + vector search
jwalinsshah Apr 22, 2026
a57ff1c
feat(life_capture): hybrid_search combining vector + keyword + recency
jwalinsshah Apr 22, 2026
0f9d9b1
feat(life_capture): controller schemas + RPC handlers (get_stats, sea…
jwalinsshah Apr 22, 2026
35eac19
test(life_capture): end-to-end (redact → quote-strip → embed → upsert…
jwalinsshah Apr 22, 2026
3ae04ff
feat(life_capture): wire PersonalIndex + Embedder into core startup
jwalinsshah Apr 22, 2026
13b3276
feat(curated_memory): snapshot_pair() — frozen capture for prompt inj…
jwalinsshah Apr 22, 2026
89797c0
feat(curated_memory): expose memory_curated.{read,add,replace,remove}…
jwalinsshah Apr 22, 2026
1019693
fix(life_capture, curated_memory): address second-opinion review find…
jwalinsshah Apr 22, 2026
2efab6a
style(life_capture, curated_memory): apply rustfmt + finish curated_m…
jwalinsshah Apr 22, 2026
b10b953
fix(life_capture, curated_memory): address CodeRabbit review findings
jwalinsshah Apr 23, 2026
0d9caa9
feat(curated_memory): wire snapshot_pair() into agent prompt builders
jwalinsshah Apr 23, 2026
b3ba61c
test(life_capture): retrieval eval fixture + runner (top-K containment)
jwalinsshah Apr 23, 2026
65a4e8a
feat(life_capture): r2d2 connection pool for concurrent WAL readers
jwalinsshah Apr 23, 2026
be1112f
feat(life_capture): add ingest controller; wire iMessage scanner to it
jwalinsshah Apr 23, 2026
87e468a
feat(eventkit): Calendar read + Reminders write bridge (A7)
jwalinsshah Apr 23, 2026
6582bfc
fix(eventkit): cache EKEventStore + fix block lifetime + dedupe corre…
jwalinsshah Apr 23, 2026
5063a5f
fix(test): add missing curated_snapshot field to ParentExecutionConte…
jwalinsshah Apr 23, 2026
1500d42
Merge remote-tracking branch 'upstream/main' into feat/a7-eventkit
jwalinsshah Apr 23, 2026
62f6bb3
merge upstream/main into feat/a7-eventkit
jwalinsshah May 4, 2026
c60c7a1
fix(agent-prompts): add curated_snapshot to PromptContext and test fi…
jwalinsshah May 4, 2026
7203b54
fix(prompts): borrow curated_snapshot from PromptContext
jwalinsshah May 4, 2026
ea0b18b
fix(agent-prompts): decouple curated snapshot type and repair merge d…
jwalinsshah May 4, 2026
60498ad
fix(agent): wire curated_snapshot on all ParentExecutionContext literals
jwalinsshah May 4, 2026
123b353
fix(agent): initialize curated snapshot in learning prompt test
jwalinsshah May 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(life_capture): strip quoted replies from email bodies
  • Loading branch information
jwalinsshah committed Apr 23, 2026
commit a938f90b9f624e307cd5c533cf5c0410ac13a2a7
58 changes: 57 additions & 1 deletion src/openhuman/life_capture/quote_strip.rs
Original file line number Diff line number Diff line change
@@ -1 +1,57 @@
// Filled in by later tasks.
use once_cell::sync::Lazy;
use regex::Regex;

static ON_DATE_WROTE: Lazy<Regex> = Lazy::new(|| {
// "On <date>, <name> <email|>(?) wrote:" — match start of any line.
Regex::new(r"(?m)^On .{1,200}\bwrote:\s*$").unwrap()
});

static OUTLOOK_SEP: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?m)^-{3,}\s*Original Message\s*-{3,}\s*$").unwrap()
});

/// Returns only the new content of an email body — drops everything from the
/// first quoted-reply marker onward, plus any line that begins with '>'.
pub fn strip_quoted_reply(body: &str) -> String {
// Cut at the earliest of the two marker types.
let mut cut: Option<usize> = None;
for re in [&*ON_DATE_WROTE, &*OUTLOOK_SEP] {
if let Some(m) = re.find(body) {
cut = Some(cut.map_or(m.start(), |c| c.min(m.start())));
}
}
let head = if let Some(idx) = cut { &body[..idx] } else { body };

// Drop any line starting with '>'.
let kept: Vec<&str> = head.lines().filter(|l| !l.trim_start().starts_with('>')).collect();
kept.join("\n").trim().to_string()
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn strips_on_date_wrote_block_and_below() {
let input = "Quick reply.\n\nOn Mon, Apr 21, 2026 at 9:14 AM, Sarah <sarah@x> wrote:\n> earlier text\n>> deeper\n";
assert_eq!(strip_quoted_reply(input), "Quick reply.");
}

#[test]
fn strips_lines_starting_with_gt() {
let input = "new thought\n> old thought\n> > older\nnewer\n";
assert_eq!(strip_quoted_reply(input), "new thought\nnewer");
}

#[test]
fn strips_outlook_original_message_separator() {
let input = "reply text\n\n-----Original Message-----\nFrom: a@b\nTo: c@d\nSubject: ...\nbody";
assert_eq!(strip_quoted_reply(input), "reply text");
}

#[test]
fn passthrough_when_no_quote_found() {
let s = "single paragraph no markers";
assert_eq!(strip_quoted_reply(s), s);
}
}