Skip to main content

cortexa_gcc/
models.rs

1//! Data models for GCC (arXiv:2508.00031v2).
2
3use serde::{Deserialize, Serialize};
4
5/// Escape the `---` block separator so user content cannot break parsers.
6pub fn sanitize(text: &str) -> String {
7    text.replace("\n---\n", "\n\\---\n")
8}
9
10/// Reverse the escaping applied by [`sanitize`].
11pub fn desanitize(text: &str) -> String {
12    text.replace("\n\\---\n", "\n---\n")
13}
14
15/// Split markdown text on `---\n` separators while respecting escaped
16/// separators (`\---\n` produced by [`sanitize`]).  After splitting,
17/// the escaped backslash is left in place; callers should apply
18/// [`desanitize`] on individual field values.
19pub fn split_blocks(text: &str) -> Vec<String> {
20    let raw: Vec<&str> = text.split("---\n").collect();
21    let mut blocks: Vec<String> = Vec::new();
22    let mut i = 0;
23    while i < raw.len() {
24        let mut block = raw[i].to_string();
25        // If block ends with '\', the `---\n` was an escaped separator — rejoin.
26        while block.ends_with('\\') && i + 1 < raw.len() {
27            i += 1;
28            block.push_str("---\n");
29            block.push_str(raw[i]);
30        }
31        blocks.push(block);
32        i += 1;
33    }
34    blocks
35}
36
37/// A single Observation–Thought–Action step logged to `log.md`.
38/// The paper continuously logs OTA cycles as the agent executes.
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct OTARecord {
41    pub step: usize,
42    pub timestamp: String,
43    pub observation: String,
44    pub thought: String,
45    pub action: String,
46}
47
48impl OTARecord {
49    pub fn to_markdown(&self) -> String {
50        format!(
51            "### Step {} — {}\n**Observation:** {}\n\n**Thought:** {}\n\n**Action:** {}\n\n---\n",
52            self.step,
53            self.timestamp,
54            sanitize(&self.observation),
55            sanitize(&self.thought),
56            sanitize(&self.action),
57        )
58    }
59}
60
61/// A commit checkpoint as described in paper §3.2.
62/// Fields: Branch Purpose, Previous Progress Summary, This Commit's Contribution.
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct CommitRecord {
65    pub commit_id: String,
66    pub branch_name: String,
67    pub branch_purpose: String,
68    pub previous_progress_summary: String,
69    pub this_commit_contribution: String,
70    pub timestamp: String,
71}
72
73impl CommitRecord {
74    pub fn to_markdown(&self) -> String {
75        format!(
76            "## Commit `{}`\n**Timestamp:** {}\n\n**Branch Purpose:** {}\n\n\
77             **Previous Progress Summary:** {}\n\n\
78             **This Commit's Contribution:** {}\n\n---\n",
79            self.commit_id,
80            self.timestamp,
81            sanitize(&self.branch_purpose),
82            sanitize(&self.previous_progress_summary),
83            sanitize(&self.this_commit_contribution),
84        )
85    }
86}
87
88/// Branch metadata stored in `metadata.yaml` (paper §3.1).
89/// Records the intent and motivation of each branch.
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct BranchMetadata {
92    pub name: String,
93    pub purpose: String,
94    pub created_from: String,
95    pub created_at: String,
96    pub status: String, // "active" | "merged" | "abandoned"
97    pub merged_into: Option<String>,
98    pub merged_at: Option<String>,
99}
100
101/// Result of the CONTEXT command (paper §3.5).
102/// The paper fixes K=1 in experiments (most recent commit record revealed).
103#[derive(Debug, Clone)]
104pub struct ContextResult {
105    pub branch_name: String,
106    pub k: usize,
107    pub commits: Vec<CommitRecord>,
108    pub ota_records: Vec<OTARecord>,
109    pub main_roadmap: String,
110    pub metadata: Option<BranchMetadata>,
111}
112
113impl ContextResult {
114    pub fn summary(&self) -> String {
115        let mut out = format!(
116            "# CONTEXT — branch `{}` (K={})\n\n",
117            self.branch_name, self.k
118        );
119        out.push_str("## Global Roadmap\n");
120        out.push_str(&self.main_roadmap);
121        out.push_str("\n\n");
122        out.push_str(&format!("## Last {} Commit(s)\n", self.k));
123        for c in &self.commits {
124            out.push_str(&c.to_markdown());
125        }
126        if !self.ota_records.is_empty() {
127            let recent = self.ota_records.iter().rev().take(5).collect::<Vec<_>>();
128            out.push_str(&format!(
129                "\n## Recent OTA Steps (showing last {} of {})\n",
130                recent.len(),
131                self.ota_records.len()
132            ));
133            for r in recent.into_iter().rev() {
134                out.push_str(&r.to_markdown());
135            }
136        }
137        out
138    }
139}