acp/sync/
content.rs

1//! @acp:module "Bootstrap Content Generator"
2//! @acp:summary "Generates bootstrap content for AI tool files"
3//! @acp:domain cli
4//! @acp:layer service
5
6use super::tool::Tool;
7
8/// Generate bootstrap content in Markdown format
9pub fn generate_bootstrap_markdown(tool: Tool) -> String {
10    let has_mcp = tool.supports_mcp();
11    let mut content = String::new();
12
13    // Core awareness
14    content.push_str("# ACP Context\n\n");
15    content.push_str(
16        "This project uses **AI Context Protocol (ACP)** for structured AI assistance.\n\n",
17    );
18
19    // Critical constraint workflow - most important section
20    content.push_str("## Before Modifying Files\n\n");
21    content.push_str("**ALWAYS check constraints before editing any file:**\n\n");
22
23    if has_mcp {
24        content.push_str("```\n");
25        content.push_str("acp_check_constraints({ path: \"path/to/file\" })\n");
26        content.push_str("```\n\n");
27    } else {
28        content.push_str("```bash\n");
29        content.push_str("acp check path/to/file\n");
30        content.push_str("# OR query directly:\n");
31        content.push_str("jq '.constraints.by_file[\"path/to/file\"]' .acp/acp.cache.json\n");
32        content.push_str("```\n\n");
33    }
34
35    // Lock levels
36    content.push_str("### Constraint Levels\n\n");
37    content.push_str("| Level | Action Required |\n");
38    content.push_str("|-------|----------------|\n");
39    content.push_str("| `frozen` | **NEVER** modify - ask user for alternatives |\n");
40    content.push_str("| `restricted` | Explain changes and get approval first |\n");
41    content.push_str("| `approval-required` | Ask before any change |\n");
42    content.push_str("| `tests-required` | Include tests with changes |\n");
43    content.push_str("| `docs-required` | Update documentation |\n\n");
44
45    // Data files
46    content.push_str("## Data Files\n\n");
47    content.push_str("| File | Contents |\n");
48    content.push_str("|------|----------|\n");
49    content.push_str("| `.acp/acp.cache.json` | Indexed codebase structure, symbols, domains |\n");
50    content.push_str("| `.acp/acp.vars.json` | Token-efficient variable definitions |\n");
51    content.push_str("| `.acp.config.json` | Project configuration |\n\n");
52
53    // Getting more context
54    content.push_str("## Getting More Context\n\n");
55
56    if has_mcp {
57        content.push_str("Use MCP tools for queries:\n\n");
58        content.push_str("- `acp_get_architecture()` - Project overview\n");
59        content.push_str("- `acp_get_file_context({ path })` - File metadata\n");
60        content.push_str("- `acp_get_symbol_context({ name })` - Symbol details\n");
61        content.push_str("- `acp_get_hotpaths()` - Critical code paths\n");
62        content.push_str("- `acp_expand_variable({ name })` - Expand $VAR references\n");
63    } else {
64        content.push_str("Use CLI or jq for queries:\n\n");
65        content.push_str("```bash\n");
66        content.push_str("# Query symbol details\n");
67        content.push_str("acp query symbol <name>\n\n");
68        content.push_str("# Query file metadata\n");
69        content.push_str("acp query file <path>\n\n");
70        content.push_str("# List domains\n");
71        content.push_str("jq '.domains | keys' .acp/acp.cache.json\n\n");
72        content.push_str("# Find frozen files\n");
73        content.push_str("jq '.constraints.by_lock_level.frozen // []' .acp/acp.cache.json\n");
74        content.push_str("```\n");
75    }
76
77    content
78}
79
80/// Generate bootstrap content wrapped for JSON (Continue.dev)
81pub fn generate_bootstrap_json(tool: Tool) -> String {
82    let markdown = generate_bootstrap_markdown(tool);
83
84    // Create JSON config structure
85    let config = serde_json::json!({
86        "_acp": {
87            "version": "1.0.0",
88            "tool": tool.name()
89        },
90        "systemMessage": format!("{}", markdown)
91    });
92
93    serde_json::to_string_pretty(&config).unwrap_or_default()
94}
95
96/// Generate bootstrap content wrapped for YAML (Aider)
97pub fn generate_bootstrap_yaml(tool: Tool) -> String {
98    let markdown = generate_bootstrap_markdown(tool);
99
100    // Indent each line for YAML multiline string
101    let indented = markdown
102        .lines()
103        .map(|line| format!("  {}", line))
104        .collect::<Vec<_>>()
105        .join("\n");
106
107    format!(
108        "# ACP Configuration for {}\n\
109         # Generated by AI Context Protocol\n\n\
110         system-prompt: |\n{}\n",
111        tool.name(),
112        indented
113    )
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_generate_markdown_without_mcp() {
122        let content = generate_bootstrap_markdown(Tool::Cursor);
123        assert!(content.contains("# ACP Context"));
124        assert!(content.contains("acp check path/to/file"));
125        assert!(!content.contains("acp_check_constraints"));
126    }
127
128    #[test]
129    fn test_generate_markdown_with_mcp() {
130        let content = generate_bootstrap_markdown(Tool::ClaudeCode);
131        assert!(content.contains("# ACP Context"));
132        assert!(content.contains("acp_check_constraints"));
133        assert!(content.contains("acp_get_architecture"));
134    }
135
136    #[test]
137    fn test_generate_json() {
138        let content = generate_bootstrap_json(Tool::Continue);
139        assert!(content.contains("\"_acp\""));
140        assert!(content.contains("\"systemMessage\""));
141        // Verify it's valid JSON
142        let parsed: serde_json::Value = serde_json::from_str(&content).unwrap();
143        assert!(parsed.get("systemMessage").is_some());
144    }
145
146    #[test]
147    fn test_generate_yaml() {
148        let content = generate_bootstrap_yaml(Tool::Aider);
149        assert!(content.contains("system-prompt: |"));
150        assert!(content.contains("ACP Configuration for Aider"));
151    }
152}