feat(permissions): implement permission system with plan mode enforcement (M1 complete)
This commit implements the complete M1 milestone (Config & Permissions) including: - New permissions crate with Tool, Action, Mode, and PermissionManager - Three permission modes: Plan (read-only default), AcceptEdits, Code - Pattern matching for permission rules (exact match and prefix with *) - Integration with config-agent for mode-based permission management - CLI integration with --mode flag to override configured mode - Permission checks for Read, Glob, and Grep operations - Comprehensive test suite (10 tests in permissions, 4 in config, 4 in CLI) Also fixes: - Fixed failing test in tools-fs (glob pattern issue) - Improved glob_list() root extraction to handle patterns like "/*.txt" All 21 workspace tests passing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -9,4 +9,7 @@ rust-version.workspace = true
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
directories = "5"
|
||||
figment = { version = "0.10", features = ["toml", "env"] }
|
||||
permissions = { path = "../permissions" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.23.0"
|
||||
|
||||
@@ -5,6 +5,7 @@ use figment::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use permissions::{Mode, PermissionManager};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Settings {
|
||||
@@ -39,6 +40,19 @@ impl Default for Settings {
|
||||
}
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
/// Create a PermissionManager based on the configured mode
|
||||
pub fn create_permission_manager(&self) -> PermissionManager {
|
||||
let mode = Mode::from_str(&self.mode).unwrap_or(Mode::Plan);
|
||||
PermissionManager::new(mode)
|
||||
}
|
||||
|
||||
/// Get the Mode enum from the mode string
|
||||
pub fn get_mode(&self) -> Mode {
|
||||
Mode::from_str(&self.mode).unwrap_or(Mode::Plan)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_settings(project_root: Option<&str>) -> Result<Settings, figment::Error> {
|
||||
let mut fig = Figment::from(Serialized::defaults(Settings::default()));
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use config_agent::{load_settings, Settings};
|
||||
use permissions::{Mode, PermissionDecision, Tool};
|
||||
use std::{env, fs};
|
||||
|
||||
#[test]
|
||||
@@ -16,4 +17,32 @@ fn precedence_env_overrides_files() {
|
||||
fn default_mode_is_plan() {
|
||||
let s = Settings::default();
|
||||
assert_eq!(s.mode, "plan");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_create_permission_manager_with_plan_mode() {
|
||||
let s = Settings::default();
|
||||
let mgr = s.create_permission_manager();
|
||||
|
||||
// Plan mode should allow read operations
|
||||
assert_eq!(mgr.check(Tool::Read, None), PermissionDecision::Allow);
|
||||
|
||||
// Plan mode should ask for write operations
|
||||
assert_eq!(mgr.check(Tool::Write, None), PermissionDecision::Ask);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_parse_mode_from_config() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let project_file = tmp.path().join(".owlen.toml");
|
||||
fs::write(&project_file, r#"mode="code""#).unwrap();
|
||||
|
||||
let s = load_settings(Some(tmp.path().to_str().unwrap())).unwrap();
|
||||
assert_eq!(s.mode, "code");
|
||||
assert_eq!(s.get_mode(), Mode::Code);
|
||||
|
||||
let mgr = s.create_permission_manager();
|
||||
// Code mode should allow everything
|
||||
assert_eq!(mgr.check(Tool::Write, None), PermissionDecision::Allow);
|
||||
assert_eq!(mgr.check(Tool::Bash, None), PermissionDecision::Allow);
|
||||
}
|
||||
Reference in New Issue
Block a user