v1.4.0: Auto-compact streaming, Bedrock summarization support, subagent orchestration, and Galaxy rebrand continuation
Major features: - Auto-compact: triggers conversation summarization when context window >= 85%, compacts Bedrock message history to a summary pair, and tracks live context tokens - Bedrock summarization: plumbs `is_summarization` flag through translator/client/response pipeline, handles SummarizeConversation input type, and marks `summarized` in metadata - Session restore: rebuilds bedrock_message_history from persisted task messages via newly-public `convert_proto_message`, preventing empty history on reconnect - Subagent orchestration: adds SubagentQuestion/Answer/CompletionSummary event types, parent-child question routing with depth limits, retry counting, and drain methods - Summarization UI: inline SummarizationView in AI blocks with progress/finished states Refactors: - Rename WarpTheme → GalaxyTheme across ~100 files (rebrand continuation) - Rename warp_home_config_dir → galaxy_home_config_dir and related path functions - Predefined rules: replace "System Defined Rule #N" with descriptive names (e.g. "Correctness Over Speed", "Never Guess") and add lookup helpers - Usage view: replace cumulative input/output token display with live context tokens, cache hit rate calculation, and separate cache read/write stats - Telemetry: remove verbose doc comments, simplify trait definitions - Facts view: simplify delete permission check (always allow local deletion) - Remove warp_managed_paths_watcher.rs (dead code) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
eaa2ddc75e
commit
6f54e2cb30
@@ -13,7 +13,7 @@ cfg_if::cfg_if! {
|
||||
use ignore::gitignore::Gitignore;
|
||||
use async_channel::Sender;
|
||||
|
||||
const RULES_FILE_PATTERN: [&str; 2] = ["WARP.md", "AGENTS.md"];
|
||||
const RULES_FILE_PATTERN: [&str; 4] = ["GALAXY.md", "WARP.md", "CLAUDE.md", "AGENTS.md"];
|
||||
const MAX_SCAN_DEPTH: usize = 3;
|
||||
const MAX_FILES_TO_SCAN: usize = 5000;
|
||||
}
|
||||
@@ -28,13 +28,23 @@ pub struct ProjectRule {
|
||||
#[derive(Debug, Default)]
|
||||
struct RuleAtPath {
|
||||
parent_path: PathBuf,
|
||||
galaxy_md: Option<ProjectRule>,
|
||||
warp_md: Option<ProjectRule>,
|
||||
claude_md: Option<ProjectRule>,
|
||||
agents_md: Option<ProjectRule>,
|
||||
}
|
||||
|
||||
impl RuleAtPath {
|
||||
fn respected_rule(&self) -> Option<&ProjectRule> {
|
||||
self.warp_md.as_ref().or(self.agents_md.as_ref())
|
||||
fn all_rules(&self) -> Vec<&ProjectRule> {
|
||||
[
|
||||
self.galaxy_md.as_ref(),
|
||||
self.warp_md.as_ref(),
|
||||
self.claude_md.as_ref(),
|
||||
self.agents_md.as_ref(),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,12 +92,14 @@ impl ProjectRules {
|
||||
|
||||
// Collect all applicable rules (rules in directories that are ancestors of the target path)
|
||||
for rule in &self.rules {
|
||||
if let Some(respected_rule) = rule.respected_rule() {
|
||||
// Check if the rule's directory is an ancestor of or equal to the target path
|
||||
if path.starts_with(&rule.parent_path) {
|
||||
active_rules.push(respected_rule.clone());
|
||||
} else {
|
||||
available_rule_paths.push(respected_rule.path.to_string_lossy().to_string());
|
||||
if path.starts_with(&rule.parent_path) {
|
||||
for project_rule in rule.all_rules() {
|
||||
active_rules.push(project_rule.clone());
|
||||
}
|
||||
} else {
|
||||
for project_rule in rule.all_rules() {
|
||||
available_rule_paths
|
||||
.push(project_rule.path.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,16 +121,16 @@ impl ProjectRules {
|
||||
.iter_mut()
|
||||
.find(|rule| rule.parent_path == parent)?;
|
||||
|
||||
if file_name.to_lowercase() == "warp.md" {
|
||||
rule.warp_md.take()
|
||||
} else if file_name.to_lowercase() == "agents.md" {
|
||||
rule.agents_md.take()
|
||||
} else {
|
||||
None
|
||||
match file_name.to_lowercase().as_str() {
|
||||
"galaxy.md" => rule.galaxy_md.take(),
|
||||
"warp.md" => rule.warp_md.take(),
|
||||
"claude.md" => rule.claude_md.take(),
|
||||
"agents.md" => rule.agents_md.take(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Upsert a rule to the set of project rules. This will create a new RuleAtPath entry if none exists and update the existin one
|
||||
/// Upsert a rule to the set of project rules. This will create a new RuleAtPath entry if none exists and update the existing one
|
||||
/// otherwise.
|
||||
#[cfg_attr(not(feature = "local_fs"), allow(dead_code))]
|
||||
fn upsert_rule(&mut self, path: &Path, content: String) {
|
||||
@@ -139,32 +151,29 @@ impl ProjectRules {
|
||||
content,
|
||||
});
|
||||
|
||||
match existing_rule {
|
||||
Some(rule) => {
|
||||
if file_name.to_lowercase() == "warp.md" {
|
||||
rule.warp_md = rule_file;
|
||||
} else if file_name.to_lowercase() == "agents.md" {
|
||||
rule.agents_md = rule_file;
|
||||
}
|
||||
}
|
||||
let rule_ref = match existing_rule {
|
||||
Some(rule) => rule,
|
||||
None => {
|
||||
let mut rule = RuleAtPath {
|
||||
self.rules.push(RuleAtPath {
|
||||
parent_path: parent.to_path_buf(),
|
||||
..Default::default()
|
||||
};
|
||||
if file_name.to_lowercase() == "warp.md" {
|
||||
rule.warp_md = rule_file;
|
||||
} else if file_name.to_lowercase() == "agents.md" {
|
||||
rule.agents_md = rule_file;
|
||||
}
|
||||
self.rules.push(rule);
|
||||
});
|
||||
self.rules.last_mut().unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
match file_name.to_lowercase().as_str() {
|
||||
"galaxy.md" => rule_ref.galaxy_md = rule_file,
|
||||
"warp.md" => rule_ref.warp_md = rule_file,
|
||||
"claude.md" => rule_ref.claude_md = rule_file,
|
||||
"agents.md" => rule_ref.agents_md = rule_file,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Singleton model that keeps track of mapping between paths and rule files
|
||||
/// Currently supports WARP.md files, but designed to be extensible
|
||||
/// Supports GALAXY.md, WARP.md, CLAUDE.md, and AGENTS.md project rule files
|
||||
#[cfg_attr(not(feature = "local_fs"), allow(dead_code))]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ProjectContextModel {
|
||||
@@ -237,18 +246,12 @@ impl ProjectContextModel {
|
||||
discovered_rules: rule_files
|
||||
.rules
|
||||
.iter()
|
||||
.filter_map(|rule| {
|
||||
rule.warp_md.as_ref().map(|rule| ProjectRulePath {
|
||||
.flat_map(|rule| {
|
||||
rule.all_rules().into_iter().map(|r| ProjectRulePath {
|
||||
project_root: root_clone.clone(),
|
||||
path: rule.path.clone(),
|
||||
path: r.path.clone(),
|
||||
})
|
||||
})
|
||||
.chain(rule_files.rules.iter().filter_map(|rule| {
|
||||
rule.agents_md.as_ref().map(|rule| ProjectRulePath {
|
||||
project_root: root_clone.clone(),
|
||||
path: rule.path.clone(),
|
||||
})
|
||||
}))
|
||||
.collect(),
|
||||
deleted_rules: Default::default(),
|
||||
};
|
||||
@@ -489,7 +492,7 @@ impl ProjectContextModel {
|
||||
(existing_rules, rules_delta)
|
||||
}
|
||||
|
||||
/// Scan a directory for rule files (currently WARP.md, extensible for future file types)
|
||||
/// Scan a directory for rule files (GALAXY.md, WARP.md, CLAUDE.md, AGENTS.md)
|
||||
/// Uses repo_metadata::entry::build_tree for efficient directory traversal
|
||||
#[cfg(feature = "local_fs")]
|
||||
async fn scan_directory_for_rules(dir_path: &Path) -> Result<ProjectRules> {
|
||||
@@ -576,11 +579,10 @@ impl ProjectContextModel {
|
||||
|
||||
pub fn indexed_rules(&self) -> impl Iterator<Item = PathBuf> + '_ {
|
||||
self.path_to_rules.values().flat_map(|rules| {
|
||||
rules.rules.iter().filter_map(|rules| {
|
||||
rules
|
||||
.respected_rule()
|
||||
.map(|project_rule| project_rule.path.clone())
|
||||
})
|
||||
rules
|
||||
.rules
|
||||
.iter()
|
||||
.flat_map(|rule| rule.all_rules().into_iter().map(|r| r.path.clone()))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -590,8 +592,9 @@ impl ProjectContextModel {
|
||||
.get(workspace_path)
|
||||
.into_iter()
|
||||
.flat_map(|rules| {
|
||||
rules.rules.iter().filter_map(|rule| {
|
||||
rule.respected_rule()
|
||||
rules.rules.iter().flat_map(|rule| {
|
||||
rule.all_rules()
|
||||
.into_iter()
|
||||
.map(|project_rule| project_rule.path.clone())
|
||||
})
|
||||
})
|
||||
|
||||
@@ -121,12 +121,12 @@ fn test_find_applicable_rules_handles_root_path() {
|
||||
|
||||
#[test]
|
||||
fn test_find_applicable_rules_complex_scenario() {
|
||||
// This test covers the example from the original request:
|
||||
// For path /a/b/c/file.rs with rules:
|
||||
// - /a/WARP.md
|
||||
// - /a/AGENTS.md
|
||||
// - /a/b/WARP.md
|
||||
// - /a/b/AGENTS.md
|
||||
// All ancestor rule files should be included.
|
||||
let mut rules = ProjectRules::default();
|
||||
|
||||
rules.upsert_rule(Path::new("/a/WARP.md"), "a_warp".to_string());
|
||||
@@ -138,13 +138,13 @@ fn test_find_applicable_rules_complex_scenario() {
|
||||
let path = PathBuf::from("/a/b/c/file.rs");
|
||||
|
||||
let result = rules.find_active_or_applicable_rules(&path).active_rules;
|
||||
assert_eq!(result.len(), 2);
|
||||
assert_eq!(result.len(), 4);
|
||||
|
||||
// Expect only WARP.md files to be included as they have higher priority.
|
||||
assert_eq!(result[0].path, PathBuf::from("/a/WARP.md"));
|
||||
assert_eq!(result[0].content, "a_warp");
|
||||
assert_eq!(result[1].path, PathBuf::from("/a/b/WARP.md"));
|
||||
assert_eq!(result[1].content, "ab_warp");
|
||||
let paths: Vec<PathBuf> = result.iter().map(|r| r.path.clone()).collect();
|
||||
assert!(paths.contains(&PathBuf::from("/a/WARP.md")));
|
||||
assert!(paths.contains(&PathBuf::from("/a/AGENTS.md")));
|
||||
assert!(paths.contains(&PathBuf::from("/a/b/WARP.md")));
|
||||
assert!(paths.contains(&PathBuf::from("/a/b/AGENTS.md")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user