Skip to main content

aptu_core/ai/
context.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Best practices context for AI system prompts.
4//!
5//! Provides current tooling recommendations to prevent AI from suggesting
6//! outdated tools (mypy, bandit, eslint+prettier) and ensure modern
7//! alternatives (ruff, pyright, biome, uv) are recommended.
8
9/// Best practices context for 2026 tooling recommendations.
10///
11/// This context is injected into all system prompts to ensure AI provides
12/// current, modern recommendations for Python, JavaScript/TypeScript, Rust,
13/// and AI models.
14pub const TOOLING_CONTEXT: &str = r"## Best Practices Context (2026)
15
16When providing recommendations, use these current best practices:
17
18### Python
19- **Linting & Formatting**: Use `ruff` (replaces flake8, isort, black)
20- **Type Checking**: Use `pyright` (replaces mypy for better performance and accuracy)
21- **Security Scanning**: Use `ruff` with security rules (replaces bandit)
22- **Package Management**: Use `uv` (fast, modern replacement for pip/poetry)
23- **Testing**: pytest with pytest-cov for coverage
24
25### JavaScript/TypeScript
26- **Linting & Formatting**: Use `biome` (replaces eslint+prettier with better performance)
27- **Type Checking**: Use TypeScript with strict mode
28- **Package Management**: Use `bun` (fastest, all-in-one toolkit) or `pnpm` (fast, efficient). Avoid npm/yarn.
29- **Testing**: Use `vitest` (Vite-native, replaces jest) or `bun test` (if using Bun runtime)
30
31### Rust
32- **Edition**: Check project's Cargo.toml for edition (2021 or 2024), use appropriate idioms
33- **Formatting**: Use `rustfmt` (built-in)
34- **Linting**: Use `clippy` (built-in)
35- **Testing**: Use built-in test framework with `cargo test`
36- **Code Style**: Prefer iterators over loops, use `?` for error propagation
37
38### AI Models (2026)
39- **Claude**: Sonnet/Opus 4.5 (NOT 3.x)
40- **OpenAI**: GPT-5.2/5.1 (NOT GPT-4)
41- **Google**: Gemini 3 Flash/Pro (NOT 2.x/1.x)
42- Consider cost, latency, and capability trade-offs";
43
44/// Loads custom guidance from configuration if available.
45///
46/// # Arguments
47///
48/// * `custom_guidance` - Optional custom guidance string from config
49///
50/// # Returns
51///
52/// A formatted string combining default [`TOOLING_CONTEXT`] with custom guidance,
53/// or just [`TOOLING_CONTEXT`] if no custom guidance is provided.
54#[must_use]
55pub fn load_custom_guidance(custom_guidance: Option<&str>) -> String {
56    match custom_guidance {
57        Some(guidance) => format!("{TOOLING_CONTEXT}\n\n## Custom Guidance\n\n{guidance}"),
58        None => TOOLING_CONTEXT.to_string(),
59    }
60}
61
62/// Load a system prompt override from `~/.config/aptu/prompts/<name>.md`.
63/// Returns the file content if the file exists and is readable, or `None` otherwise.
64pub async fn load_system_prompt_override(name: &str) -> Option<String> {
65    let path = crate::config::prompts_dir().join(format!("{name}.md"));
66    tokio::fs::read_to_string(&path).await.ok()
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_tooling_context_contains_python_recommendations() {
75        assert!(TOOLING_CONTEXT.contains("ruff"));
76        assert!(TOOLING_CONTEXT.contains("pyright"));
77        assert!(TOOLING_CONTEXT.contains("uv"));
78    }
79
80    #[test]
81    fn test_tooling_context_contains_javascript_recommendations() {
82        assert!(TOOLING_CONTEXT.contains("biome"));
83        assert!(TOOLING_CONTEXT.contains("bun"));
84        assert!(TOOLING_CONTEXT.contains("vitest"));
85    }
86
87    #[test]
88    fn test_tooling_context_contains_rust_recommendations() {
89        assert!(TOOLING_CONTEXT.contains("Cargo.toml"));
90        assert!(TOOLING_CONTEXT.contains("iterators"));
91    }
92
93    #[test]
94    fn test_tooling_context_contains_current_ai_models() {
95        assert!(TOOLING_CONTEXT.contains("Sonnet/Opus 4.5"));
96        assert!(TOOLING_CONTEXT.contains("GPT-5.2"));
97        assert!(TOOLING_CONTEXT.contains("Gemini 3"));
98        assert!(TOOLING_CONTEXT.contains("NOT 3.x"));
99        assert!(TOOLING_CONTEXT.contains("NOT GPT-4"));
100        assert!(TOOLING_CONTEXT.contains("NOT 2.x/1.x"));
101    }
102
103    #[test]
104    fn test_load_custom_guidance_without_custom() {
105        let result = load_custom_guidance(None);
106        assert_eq!(result, TOOLING_CONTEXT);
107    }
108
109    #[test]
110    fn test_load_custom_guidance_with_custom() {
111        let custom = "Use poetry instead of uv for this project";
112        let result = load_custom_guidance(Some(custom));
113        assert!(result.contains(TOOLING_CONTEXT));
114        assert!(result.contains("Custom Guidance"));
115        assert!(result.contains(custom));
116    }
117
118    #[tokio::test]
119    async fn test_load_system_prompt_override_returns_none_when_absent() {
120        let result = load_system_prompt_override("__nonexistent_test_override__").await;
121        assert!(result.is_none());
122    }
123
124    #[tokio::test]
125    async fn test_load_system_prompt_override_returns_content_when_present() {
126        use std::io::Write;
127        let dir = tempfile::tempdir().expect("create tempdir");
128        let file_path = dir.path().join("test_override.md");
129        let mut f = std::fs::File::create(&file_path).expect("create file");
130        writeln!(f, "Custom override content").expect("write file");
131        drop(f);
132
133        let content = tokio::fs::read_to_string(&file_path).await.ok();
134        assert_eq!(content.as_deref(), Some("Custom override content\n"));
135    }
136}