Detailed changes
@@ -52,6 +52,7 @@ dependencies = [
name = "agent"
version = "0.1.0"
dependencies = [
+ "agent_rules",
"anyhow",
"assistant_context_editor",
"assistant_settings",
@@ -161,6 +162,19 @@ dependencies = [
"workspace-hack",
]
+[[package]]
+name = "agent_rules"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "fs",
+ "gpui",
+ "indoc",
+ "prompt_store",
+ "util",
+ "worktree",
+]
+
[[package]]
name = "ahash"
version = "0.7.8"
@@ -3,6 +3,7 @@ resolver = "2"
members = [
"crates/activity_indicator",
"crates/agent",
+ "crates/agent_rules",
"crates/anthropic",
"crates/askpass",
"crates/assets",
@@ -209,6 +210,7 @@ edition = "2024"
activity_indicator = { path = "crates/activity_indicator" }
agent = { path = "crates/agent" }
+agent_rules = { path = "crates/agent_rules" }
ai = { path = "crates/ai" }
anthropic = { path = "crates/anthropic" }
askpass = { path = "crates/askpass" }
@@ -155,7 +155,7 @@ There are rules that apply to these root directories:
{{#each worktrees}}
{{#if rules_file}}
-`{{root_name}}/{{rules_file.rel_path}}`:
+`{{root_name}}/{{rules_file.path_in_worktree}}`:
``````
{{{rules_file.text}}}
@@ -19,6 +19,7 @@ test-support = [
]
[dependencies]
+agent_rules.workspace = true
anyhow.workspace = true
assistant_context_editor.workspace = true
assistant_settings.workspace = true
@@ -2581,7 +2581,7 @@ impl ActiveThread {
let label_text = match rules_files.as_slice() {
&[] => return div().into_any(),
&[rules_file] => {
- format!("Using {:?} file", rules_file.rel_path)
+ format!("Using {:?} file", rules_file.path_in_worktree)
}
rules_files => {
format!("Using {} rules files", rules_files.len())
@@ -3,6 +3,7 @@ use std::io::Write;
use std::ops::Range;
use std::sync::Arc;
+use agent_rules::load_worktree_rules_file;
use anyhow::{Context as _, Result, anyhow};
use assistant_settings::AssistantSettings;
use assistant_tool::{ActionLog, Tool, ToolWorkingSet};
@@ -21,13 +22,11 @@ use language_model::{
};
use project::git_store::{GitStore, GitStoreCheckpoint, RepositoryState};
use project::{Project, Worktree};
-use prompt_store::{
- AssistantSystemPromptContext, PromptBuilder, RulesFile, WorktreeInfoForSystemPrompt,
-};
+use prompt_store::{AssistantSystemPromptContext, PromptBuilder, WorktreeInfoForSystemPrompt};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::Settings;
-use util::{ResultExt as _, TryFutureExt as _, maybe, post_inc};
+use util::{ResultExt as _, TryFutureExt as _, post_inc};
use uuid::Uuid;
use crate::context::{AssistantContext, ContextId, format_context_as_string};
@@ -854,67 +853,36 @@ impl Thread {
let root_name = worktree.root_name().into();
let abs_path = worktree.abs_path();
- // Note that Cline supports `.clinerules` being a directory, but that is not currently
- // supported. This doesn't seem to occur often in GitHub repositories.
- const RULES_FILE_NAMES: [&'static str; 6] = [
- ".rules",
- ".cursorrules",
- ".windsurfrules",
- ".clinerules",
- ".github/copilot-instructions.md",
- "CLAUDE.md",
- ];
- let selected_rules_file = RULES_FILE_NAMES
- .into_iter()
- .filter_map(|name| {
- worktree
- .entry_for_path(name)
- .filter(|entry| entry.is_file())
- .map(|entry| (entry.path.clone(), worktree.absolutize(&entry.path)))
- })
- .next();
-
- if let Some((rel_rules_path, abs_rules_path)) = selected_rules_file {
- cx.spawn(async move |_| {
- let rules_file_result = maybe!(async move {
- let abs_rules_path = abs_rules_path?;
- let text = fs.load(&abs_rules_path).await.with_context(|| {
- format!("Failed to load assistant rules file {:?}", abs_rules_path)
- })?;
- anyhow::Ok(RulesFile {
- rel_path: rel_rules_path,
- abs_path: abs_rules_path.into(),
- text: text.trim().to_string(),
- })
- })
- .await;
- let (rules_file, rules_file_error) = match rules_file_result {
- Ok(rules_file) => (Some(rules_file), None),
- Err(err) => (
- None,
- Some(ThreadError::Message {
- header: "Error loading rules file".into(),
- message: format!("{err}").into(),
- }),
- ),
- };
- let worktree_info = WorktreeInfoForSystemPrompt {
- root_name,
- abs_path,
- rules_file,
- };
- (worktree_info, rules_file_error)
- })
- } else {
- Task::ready((
+ let rules_task = load_worktree_rules_file(fs, worktree, cx);
+ let Some(rules_task) = rules_task else {
+ return Task::ready((
WorktreeInfoForSystemPrompt {
root_name,
abs_path,
rules_file: None,
},
None,
- ))
- }
+ ));
+ };
+
+ cx.spawn(async move |_| {
+ let (rules_file, rules_file_error) = match rules_task.await {
+ Ok(rules_file) => (Some(rules_file), None),
+ Err(err) => (
+ None,
+ Some(ThreadError::Message {
+ header: "Error loading rules file".into(),
+ message: format!("{err}").into(),
+ }),
+ ),
+ };
+ let worktree_info = WorktreeInfoForSystemPrompt {
+ root_name,
+ abs_path,
+ rules_file,
+ };
+ (worktree_info, rules_file_error)
+ })
}
pub fn send_to_model(
@@ -0,0 +1,24 @@
+[package]
+name = "agent_rules"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "GPL-3.0-or-later"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/agent_rules.rs"
+doctest = false
+
+[dependencies]
+anyhow.workspace = true
+fs.workspace = true
+gpui.workspace = true
+prompt_store.workspace = true
+util.workspace = true
+worktree.workspace = true
+
+[dev-dependencies]
+indoc.workspace = true
@@ -0,0 +1 @@
+../../LICENSE-GPL
@@ -0,0 +1,51 @@
+use std::sync::Arc;
+
+use anyhow::{Context as _, Result};
+use fs::Fs;
+use gpui::{App, AppContext, Task};
+use prompt_store::SystemPromptRulesFile;
+use util::maybe;
+use worktree::Worktree;
+
+const RULES_FILE_NAMES: [&'static str; 6] = [
+ ".rules",
+ ".cursorrules",
+ ".windsurfrules",
+ ".clinerules",
+ ".github/copilot-instructions.md",
+ "CLAUDE.md",
+];
+
+pub fn load_worktree_rules_file(
+ fs: Arc<dyn Fs>,
+ worktree: &Worktree,
+ cx: &App,
+) -> Option<Task<Result<SystemPromptRulesFile>>> {
+ let selected_rules_file = RULES_FILE_NAMES
+ .into_iter()
+ .filter_map(|name| {
+ worktree
+ .entry_for_path(name)
+ .filter(|entry| entry.is_file())
+ .map(|entry| (entry.path.clone(), worktree.absolutize(&entry.path)))
+ })
+ .next();
+
+ // Note that Cline supports `.clinerules` being a directory, but that is not currently
+ // supported. This doesn't seem to occur often in GitHub repositories.
+ selected_rules_file.map(|(path_in_worktree, abs_path)| {
+ let fs = fs.clone();
+ cx.background_spawn(maybe!(async move {
+ let abs_path = abs_path?;
+ let text = fs
+ .load(&abs_path)
+ .await
+ .with_context(|| format!("Failed to load assistant rules file {:?}", abs_path))?;
+ anyhow::Ok(SystemPromptRulesFile {
+ path_in_worktree,
+ abs_path: abs_path.into(),
+ text: text.trim().to_string(),
+ })
+ }))
+ })
+}
@@ -38,12 +38,12 @@ impl AssistantSystemPromptContext {
pub struct WorktreeInfoForSystemPrompt {
pub root_name: String,
pub abs_path: Arc<Path>,
- pub rules_file: Option<RulesFile>,
+ pub rules_file: Option<SystemPromptRulesFile>,
}
#[derive(Serialize)]
-pub struct RulesFile {
- pub rel_path: Arc<Path>,
+pub struct SystemPromptRulesFile {
+ pub path_in_worktree: Arc<Path>,
pub abs_path: Arc<Path>,
pub text: String,
}