[文書] Playbook v4: ビジョン全38要素をカバーする完全版
v3 から追加: Sprint 3: 記憶アタッチメント (#58) + ドリーミング (#59) + Web ダッシュボード (#63) Sprint 4: シータサイクル (#60) + Obsidian (#62) Sprint 2: Bus データパス統合 (#65) + ブランチ戦略 (#64) 新規 Issue: #64: 並列Codexブランチ戦略 (worktree + PR) #65: Bus データパス統合 ビジョン達成度推移: 現在 32% → Sprint1後 38% → Sprint2後 47% → Sprint3後 72% → Sprint4後 93% Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a1febb67eb
commit
ec1d25887e
29 changed files with 471 additions and 196 deletions
|
|
@ -92,13 +92,11 @@ impl ApprovalCallback for RejectHighRisk {
|
|||
async fn request_approval(&self, request: &ApprovalRequest) -> ApprovalDecision {
|
||||
match request.risk_level {
|
||||
RiskLevel::Low | RiskLevel::Medium => ApprovalDecision::Approved,
|
||||
RiskLevel::High | RiskLevel::Critical => {
|
||||
ApprovalDecision::Rejected(Some(format!(
|
||||
"Tool {} requires manual approval (risk level: {})",
|
||||
request.name,
|
||||
request.risk_level.as_str()
|
||||
)))
|
||||
}
|
||||
RiskLevel::High | RiskLevel::Critical => ApprovalDecision::Rejected(Some(format!(
|
||||
"Tool {} requires manual approval (risk level: {})",
|
||||
request.name,
|
||||
request.risk_level.as_str()
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -187,7 +185,10 @@ mod tests {
|
|||
risk_level: RiskLevel::Low,
|
||||
description: "Read file".to_string(),
|
||||
};
|
||||
assert_eq!(approver.request_approval(&low_risk).await, ApprovalDecision::Approved);
|
||||
assert_eq!(
|
||||
approver.request_approval(&low_risk).await,
|
||||
ApprovalDecision::Approved
|
||||
);
|
||||
|
||||
// High risk should be rejected
|
||||
let high_risk = ApprovalRequest {
|
||||
|
|
|
|||
|
|
@ -153,9 +153,10 @@ impl Agent {
|
|||
/// Main agent execution loop
|
||||
pub async fn run(&self, prompt: &str) -> Result<AgentResult, AgentError> {
|
||||
// Execute SessionStart hooks
|
||||
let session_context = HookContext::new()
|
||||
.with_data("prompt", prompt);
|
||||
self.hook_manager.execute(&HookEvent::SessionStart, &session_context).await;
|
||||
let session_context = HookContext::new().with_data("prompt", prompt);
|
||||
self.hook_manager
|
||||
.execute(&HookEvent::SessionStart, &session_context)
|
||||
.await;
|
||||
|
||||
self.emit_event(AgentEvent::Started {
|
||||
prompt: prompt.to_string(),
|
||||
|
|
@ -220,7 +221,9 @@ impl Agent {
|
|||
let end_context = HookContext::new()
|
||||
.with_data("iterations", &result.iterations.to_string())
|
||||
.with_data("tool_calls", &result.tool_calls.to_string());
|
||||
self.hook_manager.execute(&HookEvent::SessionEnd, &end_context).await;
|
||||
self.hook_manager
|
||||
.execute(&HookEvent::SessionEnd, &end_context)
|
||||
.await;
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
|
@ -302,7 +305,9 @@ impl Agent {
|
|||
let pre_tool_context = HookContext::new()
|
||||
.with_tool(&tool_use.name)
|
||||
.with_data("input", &tool_use.input.to_string());
|
||||
self.hook_manager.execute(&HookEvent::PreTool, &pre_tool_context).await;
|
||||
self.hook_manager
|
||||
.execute(&HookEvent::PreTool, &pre_tool_context)
|
||||
.await;
|
||||
|
||||
// Execute tool
|
||||
self.emit_event(AgentEvent::ToolExecuting {
|
||||
|
|
@ -327,7 +332,9 @@ impl Agent {
|
|||
let post_tool_context = HookContext::new()
|
||||
.with_tool(&tool_use.name)
|
||||
.with_result(&output.content.to_string());
|
||||
self.hook_manager.execute(&HookEvent::PostTool, &post_tool_context).await;
|
||||
self.hook_manager
|
||||
.execute(&HookEvent::PostTool, &post_tool_context)
|
||||
.await;
|
||||
|
||||
// Create tool result
|
||||
let content = serde_json::to_string(&output.content)
|
||||
|
|
@ -349,7 +356,9 @@ impl Agent {
|
|||
let error_context = HookContext::new()
|
||||
.with_tool(&tool_use.name)
|
||||
.with_error(&e.to_string());
|
||||
self.hook_manager.execute(&HookEvent::OnError, &error_context).await;
|
||||
self.hook_manager
|
||||
.execute(&HookEvent::OnError, &error_context)
|
||||
.await;
|
||||
|
||||
// Add error as tool result
|
||||
results.push(ContentBlock::ToolResult {
|
||||
|
|
@ -397,7 +406,9 @@ impl Agent {
|
|||
let end_context = HookContext::new()
|
||||
.with_data("iterations", &result.iterations.to_string())
|
||||
.with_data("tool_calls", &result.tool_calls.to_string());
|
||||
self.hook_manager.execute(&HookEvent::SessionEnd, &end_context).await;
|
||||
self.hook_manager
|
||||
.execute(&HookEvent::SessionEnd, &end_context)
|
||||
.await;
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,11 +134,7 @@ pub struct McpToolExecutor {
|
|||
}
|
||||
|
||||
impl McpToolExecutor {
|
||||
pub fn new(
|
||||
server_name: String,
|
||||
tool: McpTool,
|
||||
manager: Arc<AsyncMutex<McpManager>>,
|
||||
) -> Self {
|
||||
pub fn new(server_name: String, tool: McpTool, manager: Arc<AsyncMutex<McpManager>>) -> Self {
|
||||
Self {
|
||||
server_name,
|
||||
tool_name: tool.name,
|
||||
|
|
@ -178,10 +174,13 @@ impl ToolExecutor for McpToolExecutor {
|
|||
async fn execute(&self, input: Value) -> Result<ToolOutput, ToolError> {
|
||||
let mut manager = self.manager.lock().await;
|
||||
|
||||
match manager.call_tool(&self.server_name, &self.tool_name, input).await {
|
||||
match manager
|
||||
.call_tool(&self.server_name, &self.tool_name, input)
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
let content = serde_json::to_string_pretty(&result)
|
||||
.unwrap_or_else(|_| result.to_string());
|
||||
let content =
|
||||
serde_json::to_string_pretty(&result).unwrap_or_else(|_| result.to_string());
|
||||
Ok(ToolOutput::success(content))
|
||||
}
|
||||
Err(e) => Err(ToolError::ExecutionFailed(e.to_string())),
|
||||
|
|
@ -270,7 +269,10 @@ impl ExecutorRegistry {
|
|||
}
|
||||
|
||||
/// Register MCP tools from an MCP manager
|
||||
pub async fn register_mcp_tools(&mut self, manager: Arc<AsyncMutex<McpManager>>) -> Result<usize, ToolError> {
|
||||
pub async fn register_mcp_tools(
|
||||
&mut self,
|
||||
manager: Arc<AsyncMutex<McpManager>>,
|
||||
) -> Result<usize, ToolError> {
|
||||
let mut count = 0;
|
||||
|
||||
// Get all tools from all servers
|
||||
|
|
@ -283,11 +285,7 @@ impl ExecutorRegistry {
|
|||
|
||||
for (server_name, tools) in all_tools {
|
||||
for tool in tools {
|
||||
let executor = McpToolExecutor::new(
|
||||
server_name.clone(),
|
||||
tool,
|
||||
manager.clone(),
|
||||
);
|
||||
let executor = McpToolExecutor::new(server_name.clone(), tool, manager.clone());
|
||||
self.register(executor);
|
||||
count += 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@ mod approval_integration_tests {
|
|||
let registry = ExecutorRegistry::with_standard_tools();
|
||||
|
||||
// Agent should be created successfully with AutoApproveAll callback
|
||||
let _agent = Agent::new(client, registry)
|
||||
.with_approval_callback(AutoApproveAll);
|
||||
let _agent = Agent::new(client, registry).with_approval_callback(AutoApproveAll);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -23,8 +22,7 @@ mod approval_integration_tests {
|
|||
let registry = ExecutorRegistry::with_standard_tools();
|
||||
|
||||
// Agent should be created successfully with RejectHighRisk callback
|
||||
let _agent = Agent::new(client, registry)
|
||||
.with_approval_callback(RejectHighRisk);
|
||||
let _agent = Agent::new(client, registry).with_approval_callback(RejectHighRisk);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -257,7 +257,9 @@ mod tests {
|
|||
let cache = TTLCache::new(Duration::from_secs(3600)); // default 1 hour
|
||||
|
||||
// Insert with shorter custom TTL
|
||||
cache.insert_with_ttl("key1", "value1", Duration::from_millis(50)).await;
|
||||
cache
|
||||
.insert_with_ttl("key1", "value1", Duration::from_millis(50))
|
||||
.await;
|
||||
assert_eq!(cache.get(&"key1").await, Some("value1"));
|
||||
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
|
|
@ -301,7 +303,9 @@ mod tests {
|
|||
let cache = TTLCache::new(Duration::from_millis(50));
|
||||
|
||||
cache.insert("key1", "value1").await;
|
||||
cache.insert_with_ttl("key2", "value2", Duration::from_secs(3600)).await;
|
||||
cache
|
||||
.insert_with_ttl("key2", "value2", Duration::from_secs(3600))
|
||||
.await;
|
||||
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
|
||||
|
|
@ -437,7 +441,9 @@ mod tests {
|
|||
assert!(cache.get(&key).await.is_none());
|
||||
|
||||
// Insert
|
||||
cache.insert(key.clone(), "Response from LLM".to_string()).await;
|
||||
cache
|
||||
.insert(key.clone(), "Response from LLM".to_string())
|
||||
.await;
|
||||
|
||||
// Hit
|
||||
let cached = cache.get(&key).await;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ pub struct Config {
|
|||
pub tools: ToolConfig,
|
||||
}
|
||||
|
||||
|
||||
/// API configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
|
|
|
|||
|
|
@ -568,8 +568,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_task_with_priority() {
|
||||
let task = Task::new("priority-task", "Task with priority")
|
||||
.with_priority(10);
|
||||
let task = Task::new("priority-task", "Task with priority").with_priority(10);
|
||||
|
||||
assert_eq!(task.priority, 10);
|
||||
assert_eq!(task.name, "priority-task");
|
||||
|
|
@ -577,8 +576,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_task_with_estimated_time() {
|
||||
let task = Task::new("timed-task", "Task with time estimate")
|
||||
.with_estimated_time(120);
|
||||
let task = Task::new("timed-task", "Task with time estimate").with_estimated_time(120);
|
||||
|
||||
assert_eq!(task.estimated_time, Some(120));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -315,7 +315,9 @@ mod tests {
|
|||
fn test_fallback_strategy_lower_temperature() {
|
||||
let strategy = FallbackStrategy::lower_temperature();
|
||||
match strategy {
|
||||
FallbackStrategy::RetryWithLowerTemperature { temperature_reduction } => {
|
||||
FallbackStrategy::RetryWithLowerTemperature {
|
||||
temperature_reduction,
|
||||
} => {
|
||||
assert_eq!(temperature_reduction, 0.2);
|
||||
}
|
||||
_ => panic!("Expected RetryWithLowerTemperature"),
|
||||
|
|
|
|||
|
|
@ -66,7 +66,10 @@ impl FeatureFlagManager {
|
|||
/// `true` if the flag exists and is enabled, `false` otherwise
|
||||
pub fn is_enabled(&self, flag_name: &str) -> bool {
|
||||
let flags = self.flags.read().unwrap();
|
||||
flags.get(flag_name).map(|flag| flag.enabled).unwrap_or(false)
|
||||
flags
|
||||
.get(flag_name)
|
||||
.map(|flag| flag.enabled)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Set a feature flag
|
||||
|
|
@ -190,7 +193,10 @@ impl FeatureFlagManager {
|
|||
/// HashMap of flag names to enabled status
|
||||
pub fn export_to_map(&self) -> HashMap<String, bool> {
|
||||
let flags = self.flags.read().unwrap();
|
||||
flags.iter().map(|(name, flag)| (name.clone(), flag.enabled)).collect()
|
||||
flags
|
||||
.iter()
|
||||
.map(|(name, flag)| (name.clone(), flag.enabled))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Clear all feature flags
|
||||
|
|
|
|||
|
|
@ -29,14 +29,12 @@ pub fn find_git_root(start_path: Option<&Path>) -> Result<PathBuf> {
|
|||
};
|
||||
|
||||
match git2::Repository::discover(&search_path) {
|
||||
Ok(repo) => repo
|
||||
.workdir()
|
||||
.map(|p| p.to_path_buf())
|
||||
.ok_or_else(|| {
|
||||
Error::Git(
|
||||
"Repository is bare (no working directory). Miyabi requires a non-bare repository.".to_string()
|
||||
)
|
||||
}),
|
||||
Ok(repo) => repo.workdir().map(|p| p.to_path_buf()).ok_or_else(|| {
|
||||
Error::Git(
|
||||
"Repository is bare (no working directory). Miyabi requires a non-bare repository."
|
||||
.to_string(),
|
||||
)
|
||||
}),
|
||||
Err(e) => Err(Error::Git(format!(
|
||||
"Not in a Git repository. Please run miyabi from within a Git repository.\n\
|
||||
Searched from: {:?}\n\
|
||||
|
|
|
|||
|
|
@ -139,8 +139,7 @@ impl GitHubClient {
|
|||
|
||||
/// Create from environment variables
|
||||
pub fn from_env() -> Result<Self> {
|
||||
let token = std::env::var("GITHUB_TOKEN")
|
||||
.map_err(|_| anyhow!("GITHUB_TOKEN not set"))?;
|
||||
let token = std::env::var("GITHUB_TOKEN").map_err(|_| anyhow!("GITHUB_TOKEN not set"))?;
|
||||
let repo = std::env::var("GITHUB_REPOSITORY")
|
||||
.or_else(|_| std::env::var("REPOSITORY"))
|
||||
.map_err(|_| anyhow!("GITHUB_REPOSITORY or REPOSITORY not set"))?;
|
||||
|
|
@ -156,7 +155,11 @@ impl GitHubClient {
|
|||
// ========== Issues ==========
|
||||
|
||||
/// List issues
|
||||
pub async fn list_issues(&self, state: Option<&str>, labels: Option<&str>) -> Result<Vec<Issue>> {
|
||||
pub async fn list_issues(
|
||||
&self,
|
||||
state: Option<&str>,
|
||||
labels: Option<&str>,
|
||||
) -> Result<Vec<Issue>> {
|
||||
let mut url = format!(
|
||||
"{}/repos/{}/{}/issues",
|
||||
self.base_url, self.owner, self.repo
|
||||
|
|
@ -230,7 +233,8 @@ impl GitHubClient {
|
|||
self.base_url, self.owner, self.repo, issue_number
|
||||
);
|
||||
|
||||
let response = self.client
|
||||
let response = self
|
||||
.client
|
||||
.post(&url)
|
||||
.json(&serde_json::json!({ "labels": labels }))
|
||||
.send()
|
||||
|
|
@ -253,7 +257,8 @@ impl GitHubClient {
|
|||
self.base_url, self.owner, self.repo, number
|
||||
);
|
||||
|
||||
let response = self.client
|
||||
let response = self
|
||||
.client
|
||||
.patch(&url)
|
||||
.json(&serde_json::json!({ "state": "closed" }))
|
||||
.send()
|
||||
|
|
@ -273,10 +278,7 @@ impl GitHubClient {
|
|||
|
||||
/// List pull requests
|
||||
pub async fn list_pull_requests(&self, state: Option<&str>) -> Result<Vec<PullRequest>> {
|
||||
let mut url = format!(
|
||||
"{}/repos/{}/{}/pulls",
|
||||
self.base_url, self.owner, self.repo
|
||||
);
|
||||
let mut url = format!("{}/repos/{}/{}/pulls", self.base_url, self.owner, self.repo);
|
||||
|
||||
if let Some(s) = state {
|
||||
url = format!("{}?state={}", url, s);
|
||||
|
|
@ -314,11 +316,11 @@ impl GitHubClient {
|
|||
}
|
||||
|
||||
/// Create a pull request
|
||||
pub async fn create_pull_request(&self, request: CreatePullRequestRequest) -> Result<PullRequest> {
|
||||
let url = format!(
|
||||
"{}/repos/{}/{}/pulls",
|
||||
self.base_url, self.owner, self.repo
|
||||
);
|
||||
pub async fn create_pull_request(
|
||||
&self,
|
||||
request: CreatePullRequestRequest,
|
||||
) -> Result<PullRequest> {
|
||||
let url = format!("{}/repos/{}/{}/pulls", self.base_url, self.owner, self.repo);
|
||||
|
||||
let response = self.client.post(&url).json(&request).send().await?;
|
||||
|
||||
|
|
@ -333,7 +335,11 @@ impl GitHubClient {
|
|||
}
|
||||
|
||||
/// Merge a pull request
|
||||
pub async fn merge_pull_request(&self, number: u64, commit_message: Option<&str>) -> Result<()> {
|
||||
pub async fn merge_pull_request(
|
||||
&self,
|
||||
number: u64,
|
||||
commit_message: Option<&str>,
|
||||
) -> Result<()> {
|
||||
let url = format!(
|
||||
"{}/repos/{}/{}/pulls/{}/merge",
|
||||
self.base_url, self.owner, self.repo, number
|
||||
|
|
@ -403,10 +409,7 @@ impl GitHubClient {
|
|||
|
||||
/// Get repository information
|
||||
pub async fn get_repo(&self) -> Result<serde_json::Value> {
|
||||
let url = format!(
|
||||
"{}/repos/{}/{}",
|
||||
self.base_url, self.owner, self.repo
|
||||
);
|
||||
let url = format!("{}/repos/{}/{}", self.base_url, self.owner, self.repo);
|
||||
|
||||
let response = self.client.get(&url).send().await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -270,7 +270,11 @@ impl HookManager {
|
|||
let expanded_title = self.expand_variables(title, context);
|
||||
let expanded_message = self.expand_variables(message, context);
|
||||
// For now, just log the notification
|
||||
tracing::info!("Hook notification: {} - {}", expanded_title, expanded_message);
|
||||
tracing::info!(
|
||||
"Hook notification: {} - {}",
|
||||
expanded_title,
|
||||
expanded_message
|
||||
);
|
||||
HookResult {
|
||||
hook_name: hook.name.clone(),
|
||||
success: true,
|
||||
|
|
|
|||
|
|
@ -136,7 +136,11 @@ impl McpServer {
|
|||
}
|
||||
|
||||
/// Send a request and get a response
|
||||
pub async fn request(&mut self, method: &str, params: Option<serde_json::Value>) -> Result<McpResponse> {
|
||||
pub async fn request(
|
||||
&mut self,
|
||||
method: &str,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<McpResponse> {
|
||||
self.request_id += 1;
|
||||
let request = McpRequest {
|
||||
jsonrpc: "2.0".to_string(),
|
||||
|
|
@ -145,9 +149,15 @@ impl McpServer {
|
|||
params,
|
||||
};
|
||||
|
||||
let stdin = self.process.stdin.as_mut()
|
||||
let stdin = self
|
||||
.process
|
||||
.stdin
|
||||
.as_mut()
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to get stdin"))?;
|
||||
let stdout = self.process.stdout.as_mut()
|
||||
let stdout = self
|
||||
.process
|
||||
.stdout
|
||||
.as_mut()
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to get stdout"))?;
|
||||
|
||||
// Send request
|
||||
|
|
@ -183,7 +193,9 @@ impl McpServer {
|
|||
return Err(anyhow::anyhow!("MCP error: {}", error.message));
|
||||
}
|
||||
|
||||
response.result.ok_or_else(|| anyhow::anyhow!("No result in response"))
|
||||
response
|
||||
.result
|
||||
.ok_or_else(|| anyhow::anyhow!("No result in response"))
|
||||
}
|
||||
|
||||
/// List available tools
|
||||
|
|
@ -193,16 +205,25 @@ impl McpServer {
|
|||
return Err(anyhow::anyhow!("MCP error: {}", error.message));
|
||||
}
|
||||
|
||||
let result = response.result.ok_or_else(|| anyhow::anyhow!("No result"))?;
|
||||
let result = response
|
||||
.result
|
||||
.ok_or_else(|| anyhow::anyhow!("No result"))?;
|
||||
let tools: Vec<McpTool> = serde_json::from_value(
|
||||
result.get("tools").cloned().unwrap_or(serde_json::Value::Array(vec![]))
|
||||
result
|
||||
.get("tools")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Array(vec![])),
|
||||
)?;
|
||||
|
||||
Ok(tools)
|
||||
}
|
||||
|
||||
/// Call a tool
|
||||
pub async fn call_tool(&mut self, name: &str, arguments: serde_json::Value) -> Result<serde_json::Value> {
|
||||
pub async fn call_tool(
|
||||
&mut self,
|
||||
name: &str,
|
||||
arguments: serde_json::Value,
|
||||
) -> Result<serde_json::Value> {
|
||||
let params = serde_json::json!({
|
||||
"name": name,
|
||||
"arguments": arguments
|
||||
|
|
@ -424,7 +445,10 @@ command: node
|
|||
let config = McpServerConfig {
|
||||
name: "filesystem".to_string(),
|
||||
command: "npx".to_string(),
|
||||
args: vec!["-y".to_string(), "@anthropic/mcp-server-filesystem".to_string()],
|
||||
args: vec![
|
||||
"-y".to_string(),
|
||||
"@anthropic/mcp-server-filesystem".to_string(),
|
||||
],
|
||||
env: {
|
||||
let mut env = HashMap::new();
|
||||
env.insert("HOME".to_string(), "/tmp".to_string());
|
||||
|
|
@ -616,15 +640,13 @@ command: node
|
|||
#[test]
|
||||
fn test_mcp_manager_is_running() {
|
||||
let config = McpConfig {
|
||||
servers: vec![
|
||||
McpServerConfig {
|
||||
name: "server1".to_string(),
|
||||
command: "cmd".to_string(),
|
||||
args: vec![],
|
||||
env: HashMap::new(),
|
||||
auto_start: false,
|
||||
},
|
||||
],
|
||||
servers: vec![McpServerConfig {
|
||||
name: "server1".to_string(),
|
||||
command: "cmd".to_string(),
|
||||
args: vec![],
|
||||
env: HashMap::new(),
|
||||
auto_start: false,
|
||||
}],
|
||||
};
|
||||
|
||||
let manager = McpManager::with_config(config);
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ impl OpenClawClient {
|
|||
message: message.to_string(),
|
||||
};
|
||||
|
||||
let response = self.client
|
||||
let response = self
|
||||
.client
|
||||
.post(format!("{}/api/message", self.gateway_url))
|
||||
.header("Authorization", format!("Bearer {}", self.token))
|
||||
.json(&payload)
|
||||
|
|
|
|||
|
|
@ -327,8 +327,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_orchestrator_task_with_system_prompt() {
|
||||
let task = OrchestratorTask::new("task-1", "Do something")
|
||||
.with_system_prompt("You are helpful");
|
||||
let task =
|
||||
OrchestratorTask::new("task-1", "Do something").with_system_prompt("You are helpful");
|
||||
assert_eq!(task.system_prompt, Some("You are helpful".to_string()));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -178,8 +178,9 @@ impl PluginManager {
|
|||
pub fn unregister(&self, name: &str) -> Result<()> {
|
||||
let mut plugins = self.plugins.write().unwrap();
|
||||
|
||||
let mut entry =
|
||||
plugins.remove(name).ok_or_else(|| anyhow!("Plugin '{}' not found", name))?;
|
||||
let mut entry = plugins
|
||||
.remove(name)
|
||||
.ok_or_else(|| anyhow!("Plugin '{}' not found", name))?;
|
||||
|
||||
entry.plugin.shutdown()?;
|
||||
entry.state = PluginState::Shutdown;
|
||||
|
|
@ -198,10 +199,16 @@ impl PluginManager {
|
|||
pub fn execute(&self, name: &str, context: &PluginContext) -> Result<PluginResult> {
|
||||
let plugins = self.plugins.read().unwrap();
|
||||
|
||||
let entry = plugins.get(name).ok_or_else(|| anyhow!("Plugin '{}' not found", name))?;
|
||||
let entry = plugins
|
||||
.get(name)
|
||||
.ok_or_else(|| anyhow!("Plugin '{}' not found", name))?;
|
||||
|
||||
if entry.state != PluginState::Initialized {
|
||||
return Err(anyhow!("Plugin '{}' is not initialized (state: {:?})", name, entry.state));
|
||||
return Err(anyhow!(
|
||||
"Plugin '{}' is not initialized (state: {:?})",
|
||||
name,
|
||||
entry.state
|
||||
));
|
||||
}
|
||||
|
||||
entry.plugin.execute(context)
|
||||
|
|
@ -210,7 +217,10 @@ impl PluginManager {
|
|||
/// Lists all registered plugins
|
||||
pub fn list_plugins(&self) -> Vec<PluginMetadata> {
|
||||
let plugins = self.plugins.read().unwrap();
|
||||
plugins.values().map(|entry| entry.plugin.metadata()).collect()
|
||||
plugins
|
||||
.values()
|
||||
.map(|entry| entry.plugin.metadata())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Gets plugin metadata
|
||||
|
|
|
|||
|
|
@ -305,10 +305,7 @@ mod tests {
|
|||
async fn test_retry_returns_correct_value() {
|
||||
let config = RetryConfig::new(1, 10, 100);
|
||||
|
||||
let result = retry_with_backoff(config, || async {
|
||||
Ok::<i32, Error>(42)
|
||||
})
|
||||
.await;
|
||||
let result = retry_with_backoff(config, || async { Ok::<i32, Error>(42) }).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), 42);
|
||||
|
|
|
|||
|
|
@ -649,7 +649,11 @@ settings:
|
|||
let loader = RulesLoader::new(temp_dir.path().to_path_buf());
|
||||
|
||||
// Invalid YAML
|
||||
std::fs::write(temp_dir.path().join(".miyabirules"), "invalid: yaml: content:").unwrap();
|
||||
std::fs::write(
|
||||
temp_dir.path().join(".miyabirules"),
|
||||
"invalid: yaml: content:",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = loader.load();
|
||||
assert!(result.is_err());
|
||||
|
|
|
|||
|
|
@ -353,9 +353,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_session_with_system_prompt() {
|
||||
let session = Session::new("Test")
|
||||
.system_prompt("You are a helpful assistant");
|
||||
assert_eq!(session.system_prompt, Some("You are a helpful assistant".to_string()));
|
||||
let session = Session::new("Test").system_prompt("You are a helpful assistant");
|
||||
assert_eq!(
|
||||
session.system_prompt,
|
||||
Some("You are a helpful assistant".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -500,14 +500,22 @@ impl LegacyTasksFile {
|
|||
version: self.version,
|
||||
generated_at: Utc::now(),
|
||||
generated_from_event_id: None,
|
||||
tasks: self.tasks.into_iter().map(LegacyTask::into_execution_task).collect(),
|
||||
tasks: self
|
||||
.tasks
|
||||
.into_iter()
|
||||
.map(LegacyTask::into_execution_task)
|
||||
.collect(),
|
||||
file_locks: HashMap::new(),
|
||||
};
|
||||
|
||||
for task in &snapshot.tasks {
|
||||
if let Some(lock) = &task.lock {
|
||||
let owner_parts: Vec<&str> = lock.locked_by.split('@').collect();
|
||||
let agent = owner_parts.first().copied().unwrap_or("unknown").to_string();
|
||||
let agent = owner_parts
|
||||
.first()
|
||||
.copied()
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
let node = owner_parts.get(1).copied().unwrap_or("unknown").to_string();
|
||||
let expires_at = lease_expiry(lock.last_heartbeat, lock.lease_duration_sec);
|
||||
|
||||
|
|
|
|||
|
|
@ -238,12 +238,8 @@ fn convert_agent_event(event: crate::agent::AgentEvent) -> AgentStreamEvent {
|
|||
crate::agent::AgentEvent::TokenUsage { input, output } => {
|
||||
AgentStreamEvent::TokenUsage { input, output }
|
||||
}
|
||||
crate::agent::AgentEvent::Completed { result } => {
|
||||
AgentStreamEvent::Completed { result }
|
||||
}
|
||||
crate::agent::AgentEvent::Failed { error } => {
|
||||
AgentStreamEvent::Error { error }
|
||||
}
|
||||
crate::agent::AgentEvent::Completed { result } => AgentStreamEvent::Completed { result },
|
||||
crate::agent::AgentEvent::Failed { error } => AgentStreamEvent::Error { error },
|
||||
crate::agent::AgentEvent::ToolApproved { .. } => {
|
||||
// Approval events are internal, map to Started
|
||||
AgentStreamEvent::Started
|
||||
|
|
@ -254,9 +250,7 @@ fn convert_agent_event(event: crate::agent::AgentEvent) -> AgentStreamEvent {
|
|||
crate::agent::AgentEvent::ToolProgress { name, .. } => {
|
||||
AgentStreamEvent::ToolStarted { name }
|
||||
}
|
||||
crate::agent::AgentEvent::TextDelta { text } => {
|
||||
AgentStreamEvent::TextChunk { text }
|
||||
}
|
||||
crate::agent::AgentEvent::TextDelta { text } => AgentStreamEvent::TextChunk { text },
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -353,8 +347,7 @@ mod tests {
|
|||
let client = AnthropicClient::new("test-key".to_string()).unwrap();
|
||||
let registry = ExecutorRegistry::with_standard_tools();
|
||||
|
||||
let agent = StreamingAgent::new(client, registry)
|
||||
.with_system_prompt("You are helpful");
|
||||
let agent = StreamingAgent::new(client, registry).with_system_prompt("You are helpful");
|
||||
assert_eq!(agent.system_prompt, Some("You are helpful".to_string()));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,6 @@ pub enum StepCondition {
|
|||
If { expression: String },
|
||||
}
|
||||
|
||||
|
||||
/// Retry configuration for a step
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RetryConfig {
|
||||
|
|
@ -120,7 +119,6 @@ pub enum FailurePolicy {
|
|||
Cleanup,
|
||||
}
|
||||
|
||||
|
||||
/// Step execution result
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StepResult {
|
||||
|
|
@ -238,7 +236,11 @@ impl WorkflowManager {
|
|||
for entry in std::fs::read_dir(dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.extension().map(|e| e == "yml" || e == "yaml").unwrap_or(false) {
|
||||
if path
|
||||
.extension()
|
||||
.map(|e| e == "yml" || e == "yaml")
|
||||
.unwrap_or(false)
|
||||
{
|
||||
if let Ok(name) = self.load_workflow(&path) {
|
||||
loaded.push(name);
|
||||
}
|
||||
|
|
@ -264,7 +266,11 @@ impl WorkflowManager {
|
|||
}
|
||||
|
||||
/// Execute a workflow
|
||||
pub async fn execute(&self, name: &str, initial_vars: HashMap<String, String>) -> Result<WorkflowResult> {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
name: &str,
|
||||
initial_vars: HashMap<String, String>,
|
||||
) -> Result<WorkflowResult> {
|
||||
let workflow = self
|
||||
.workflows
|
||||
.get(name)
|
||||
|
|
@ -361,7 +367,10 @@ impl WorkflowManager {
|
|||
|
||||
let status = if all_succeeded {
|
||||
WorkflowStatus::Completed
|
||||
} else if step_results.iter().any(|r| r.status == StepStatus::Completed) {
|
||||
} else if step_results
|
||||
.iter()
|
||||
.any(|r| r.status == StepStatus::Completed)
|
||||
{
|
||||
WorkflowStatus::PartiallyCompleted
|
||||
} else {
|
||||
WorkflowStatus::Failed
|
||||
|
|
@ -453,24 +462,20 @@ impl WorkflowManager {
|
|||
|
||||
// Execute the task
|
||||
let result = match agent.run(&expanded_task).await {
|
||||
Ok(agent_result) => {
|
||||
StepResult {
|
||||
step_id: step.id.clone(),
|
||||
status: StepStatus::Completed,
|
||||
output: Some(agent_result.output),
|
||||
error: None,
|
||||
duration_ms: step_start.elapsed().as_millis() as u64,
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
StepResult {
|
||||
step_id: step.id.clone(),
|
||||
status: StepStatus::Failed,
|
||||
output: None,
|
||||
error: Some(e.to_string()),
|
||||
duration_ms: step_start.elapsed().as_millis() as u64,
|
||||
}
|
||||
}
|
||||
Ok(agent_result) => StepResult {
|
||||
step_id: step.id.clone(),
|
||||
status: StepStatus::Completed,
|
||||
output: Some(agent_result.output),
|
||||
error: None,
|
||||
duration_ms: step_start.elapsed().as_millis() as u64,
|
||||
},
|
||||
Err(e) => StepResult {
|
||||
step_id: step.id.clone(),
|
||||
status: StepStatus::Failed,
|
||||
output: None,
|
||||
error: Some(e.to_string()),
|
||||
duration_ms: step_start.elapsed().as_millis() as u64,
|
||||
},
|
||||
};
|
||||
|
||||
// Store output variable if specified
|
||||
|
|
@ -495,7 +500,10 @@ impl WorkflowManager {
|
|||
|
||||
let status = if all_succeeded {
|
||||
WorkflowStatus::Completed
|
||||
} else if step_results.iter().any(|r| r.status == StepStatus::Completed) {
|
||||
} else if step_results
|
||||
.iter()
|
||||
.any(|r| r.status == StepStatus::Completed)
|
||||
{
|
||||
WorkflowStatus::PartiallyCompleted
|
||||
} else {
|
||||
WorkflowStatus::Failed
|
||||
|
|
@ -517,7 +525,13 @@ impl WorkflowManager {
|
|||
|
||||
for step in &workflow.steps {
|
||||
if !visited.contains_key(&step.id) {
|
||||
self.visit_step(workflow, &step.id, &mut visited, &mut temp_visited, &mut order)?;
|
||||
self.visit_step(
|
||||
workflow,
|
||||
&step.id,
|
||||
&mut visited,
|
||||
&mut temp_visited,
|
||||
&mut order,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -534,7 +548,10 @@ impl WorkflowManager {
|
|||
order: &mut Vec<String>,
|
||||
) -> Result<()> {
|
||||
if temp_visited.get(step_id).copied().unwrap_or(false) {
|
||||
return Err(anyhow::anyhow!("Circular dependency detected at step: {}", step_id));
|
||||
return Err(anyhow::anyhow!(
|
||||
"Circular dependency detected at step: {}",
|
||||
step_id
|
||||
));
|
||||
}
|
||||
|
||||
if visited.get(step_id).copied().unwrap_or(false) {
|
||||
|
|
@ -561,7 +578,11 @@ impl WorkflowManager {
|
|||
}
|
||||
|
||||
/// Evaluate a step condition
|
||||
fn evaluate_condition(&self, condition: &Option<StepCondition>, context: &WorkflowContext) -> bool {
|
||||
fn evaluate_condition(
|
||||
&self,
|
||||
condition: &Option<StepCondition>,
|
||||
context: &WorkflowContext,
|
||||
) -> bool {
|
||||
match condition {
|
||||
None | Some(StepCondition::Always) => true,
|
||||
Some(StepCondition::OnSuccess) => context
|
||||
|
|
@ -607,19 +628,17 @@ mod tests {
|
|||
let workflow = Workflow {
|
||||
name: "test-workflow".to_string(),
|
||||
description: "Test".to_string(),
|
||||
steps: vec![
|
||||
WorkflowStep {
|
||||
id: "step1".to_string(),
|
||||
name: "First Step".to_string(),
|
||||
agent: Some("code-reviewer".to_string()),
|
||||
task: "Review code".to_string(),
|
||||
depends_on: vec![],
|
||||
condition: None,
|
||||
timeout: 300,
|
||||
retry: RetryConfig::default(),
|
||||
output: Some("review_result".to_string()),
|
||||
},
|
||||
],
|
||||
steps: vec![WorkflowStep {
|
||||
id: "step1".to_string(),
|
||||
name: "First Step".to_string(),
|
||||
agent: Some("code-reviewer".to_string()),
|
||||
task: "Review code".to_string(),
|
||||
depends_on: vec![],
|
||||
condition: None,
|
||||
timeout: 300,
|
||||
retry: RetryConfig::default(),
|
||||
output: Some("review_result".to_string()),
|
||||
}],
|
||||
variables: HashMap::new(),
|
||||
on_failure: FailurePolicy::Stop,
|
||||
};
|
||||
|
|
@ -815,7 +834,10 @@ mod tests {
|
|||
};
|
||||
|
||||
manager.register(workflow);
|
||||
let result = manager.execute("output-test", HashMap::new()).await.unwrap();
|
||||
let result = manager
|
||||
.execute("output-test", HashMap::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.status, WorkflowStatus::Completed);
|
||||
assert_eq!(result.steps.len(), 2);
|
||||
|
|
@ -834,7 +856,10 @@ mod tests {
|
|||
on_failure: FailurePolicy::Stop,
|
||||
};
|
||||
|
||||
assert_eq!(workflow.variables.get("project"), Some(&"miyabi".to_string()));
|
||||
assert_eq!(
|
||||
workflow.variables.get("project"),
|
||||
Some(&"miyabi".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -145,16 +145,15 @@ impl App {
|
|||
Ok(Some(rules)) => {
|
||||
let count = rules.rules.len();
|
||||
if count > 0 {
|
||||
view.notifications.info(
|
||||
"Rules Loaded",
|
||||
format!("{} project rules active", count),
|
||||
);
|
||||
view.notifications
|
||||
.info("Rules Loaded", format!("{} project rules active", count));
|
||||
}
|
||||
Some(rules)
|
||||
}
|
||||
Ok(None) => None,
|
||||
Err(e) => {
|
||||
view.notifications.error("Rules Error", format!("Failed to load: {}", e));
|
||||
view.notifications
|
||||
.error("Rules Error", format!("Failed to load: {}", e));
|
||||
None
|
||||
}
|
||||
};
|
||||
|
|
@ -280,9 +279,10 @@ impl App {
|
|||
.notifications
|
||||
.error("Clipboard Error", e.to_string());
|
||||
} else {
|
||||
self.view
|
||||
.notifications
|
||||
.info("Copied", format!("{} chars", text.len()));
|
||||
self.view.notifications.info(
|
||||
"Copied",
|
||||
format!("{} chars", text.len()),
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
|
|
@ -305,7 +305,8 @@ impl App {
|
|||
#[cfg(target_os = "macos")]
|
||||
let result = std::process::Command::new("open").arg(&path).spawn();
|
||||
#[cfg(target_os = "linux")]
|
||||
let result = std::process::Command::new("xdg-open").arg(&path).spawn();
|
||||
let result =
|
||||
std::process::Command::new("xdg-open").arg(&path).spawn();
|
||||
#[cfg(target_os = "windows")]
|
||||
let result = std::process::Command::new("cmd")
|
||||
.args(["/C", "start", "", &path])
|
||||
|
|
@ -316,9 +317,7 @@ impl App {
|
|||
self.view.notifications.info("Opened", &path);
|
||||
}
|
||||
Err(e) => {
|
||||
self.view
|
||||
.notifications
|
||||
.error("Open Error", e.to_string());
|
||||
self.view.notifications.error("Open Error", e.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,8 +40,7 @@ pub enum VimMode {
|
|||
}
|
||||
|
||||
/// Keybinding style preference
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Default)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum KeybindingStyle {
|
||||
/// Standard keybindings
|
||||
#[default]
|
||||
|
|
@ -52,7 +51,6 @@ pub enum KeybindingStyle {
|
|||
Emacs,
|
||||
}
|
||||
|
||||
|
||||
/// Edit operation for undo/redo
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EditOperation {
|
||||
|
|
@ -1479,12 +1477,14 @@ impl ChatComposer {
|
|||
return Style::default().bg(Color::Rgb(68, 71, 90)).fg(Color::Cyan);
|
||||
}
|
||||
}
|
||||
if line_idx == self.cursor.line && col == self.cursor.col
|
||||
&& matches!(ch, '(' | ')' | '[' | ']' | '{' | '}' | '<' | '>') {
|
||||
return Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD);
|
||||
}
|
||||
if line_idx == self.cursor.line
|
||||
&& col == self.cursor.col
|
||||
&& matches!(ch, '(' | ')' | '[' | ']' | '{' | '}' | '<' | '>')
|
||||
{
|
||||
return Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD);
|
||||
}
|
||||
|
||||
// Basic syntax highlighting
|
||||
match ch {
|
||||
|
|
|
|||
|
|
@ -42,9 +42,7 @@ pub fn render_diff_widget_with_scroll(
|
|||
|
||||
// Create block with optional title
|
||||
let block = if let Some(t) = title {
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(t.to_string())
|
||||
Block::default().borders(Borders::ALL).title(t.to_string())
|
||||
} else {
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
|
|
|
|||
|
|
@ -65,10 +65,7 @@ impl HistoryList {
|
|||
}
|
||||
}
|
||||
|
||||
let list_items: Vec<ListItem> = visible_lines
|
||||
.into_iter()
|
||||
.map(ListItem::new)
|
||||
.collect();
|
||||
let list_items: Vec<ListItem> = visible_lines.into_iter().map(ListItem::new).collect();
|
||||
|
||||
let list = List::new(list_items).style(Style::default().fg(colors::FG));
|
||||
frame.render_widget(list, inner);
|
||||
|
|
|
|||
|
|
@ -392,9 +392,11 @@ impl MainView {
|
|||
}
|
||||
_ => {}
|
||||
},
|
||||
ActiveOverlay::Help => if self.help_viewer.handle_key(key) == HelpAction::Close {
|
||||
self.close_overlay();
|
||||
},
|
||||
ActiveOverlay::Help => {
|
||||
if self.help_viewer.handle_key(key) == HelpAction::Close {
|
||||
self.close_overlay();
|
||||
}
|
||||
}
|
||||
ActiveOverlay::Approval => match self.approval_overlay.handle_key(key) {
|
||||
ApprovalAction::Approve(id) | ApprovalAction::ApproveAll(id) => {
|
||||
self.close_overlay();
|
||||
|
|
|
|||
190
docs/dtp/PLAYBOOK-v4-complete.md
Normal file
190
docs/dtp/PLAYBOOK-v4-complete.md
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
# DTP Playbook v4 — ビジョン完全準拠版
|
||||
|
||||
_v4: 原則 1(記憶)+ Bus ドッキング + ブランチ戦略を統合。全 38 要素をカバー。_
|
||||
|
||||
---
|
||||
|
||||
## North Star
|
||||
|
||||
> 記憶はアタッチメント。ジグで手順を強制。GitHub で事実を確定。
|
||||
|
||||
---
|
||||
|
||||
## 完了済み
|
||||
|
||||
| 内容 | コミット | 行数 |
|
||||
|------|---------|------|
|
||||
| Phase A: gate + lock + store + protocol | 986d907 | +1,485 |
|
||||
| Phase B: CLI サブコマンド | 273c416 | +1,171 |
|
||||
| clippy 修正 | 437b959 | 0 (既に反映済み) |
|
||||
|
||||
---
|
||||
|
||||
## Sprint 1: GATE 修正 + Phase C(今日)
|
||||
|
||||
> ビジョン対応: 原則 2 (ジグ) を 58% → 90% に
|
||||
|
||||
| # | Issue | タスク | 行数 |
|
||||
|---|-------|--------|------|
|
||||
| 1 | #52 | GATE 0: issue=0 拒否 | ~5 |
|
||||
| 2 | #53 | GATE 5: ブランチ名バリデーション | ~10 |
|
||||
| 3 | #54 | GATE 3: HIGH risk 承認チェック | ~20 |
|
||||
| 4 | #55 | GATE 7: merge 後ロック自動解放 + 後続タスク解放 | ~15 |
|
||||
| 5 | #56 | exit code: GATE 拒否=1, 入力エラー=2 | ~10 |
|
||||
| 6 | #57 | 依存ブロック assign → exit 1 | ~10 |
|
||||
| 7 | — | verify_merge: GitHub API で PR merged を検証 | ~50 |
|
||||
| 8 | — | escape hatch: force_unlock + manual_complete | ~50 |
|
||||
| 9 | — | E2E テスト: register → done 全シーケンス | ~100 |
|
||||
|
||||
**GATE**: cargo test GREEN + clippy ZERO + E2E テスト GREEN
|
||||
**タグ**: `v1.0-dtp-complete`
|
||||
|
||||
---
|
||||
|
||||
## Sprint 2: Bus ドッキング + ブランチ戦略(今週)
|
||||
|
||||
> ビジョン対応: 原則 3 (SSOT) を 44% → 80% に
|
||||
|
||||
| # | Issue | タスク | 行数 |
|
||||
|---|-------|--------|------|
|
||||
| 1 | #61 | Bus ドッキング: register → auto enqueue | ~30 |
|
||||
| 2 | #61 | Bus ドッキング: merge → auto complete | ~20 |
|
||||
| 3 | #65 | Bus データパス統合 (HAYASHI_SHUNSUKE → 参照) | ~10 |
|
||||
| 4 | #64 | 並列 Codex ブランチ戦略: assign 時に自動 branch + worktree | ~50 |
|
||||
| 5 | — | JSON 出力スキーマ統一 | ~30 |
|
||||
| 6 | — | OpenClaw hooks 連携 | ~50 |
|
||||
| 7 | — | OpenClaw main 呼び出しテスト | テスト |
|
||||
| 8 | — | tasks.json memory sync | ~30 |
|
||||
|
||||
**GATE**: Bus enqueue/complete 動作 + OpenClaw main が register→merge 完走
|
||||
**タグ**: `v1.1-bus-docked`
|
||||
|
||||
---
|
||||
|
||||
## Sprint 3: 記憶アタッチメント + ドリーミング(来週)
|
||||
|
||||
> ビジョン対応: 原則 1 (記憶) を 0% → 70% に ← **核心**
|
||||
|
||||
| # | Issue | タスク | 行数 |
|
||||
|---|-------|--------|------|
|
||||
| 1 | #58 | attach_context: Issue 本文取得 | ~20 |
|
||||
| 2 | #58 | attach_context: GNI impact 結果をアタッチ | ~20 |
|
||||
| 3 | #58 | attach_context: ロック対象ファイルの先頭 50 行抜粋 | ~20 |
|
||||
| 4 | #58 | attach_context: トークン閾値で切り詰め | ~20 |
|
||||
| 5 | #58 | tasks.json に context_attachments フィールド追加 | ~15 |
|
||||
| 6 | #58 | CLI: assign 時に attach_context 自動実行 | ~10 |
|
||||
| 7 | #58 | CLI: --format json で attachments を出力 | ~10 |
|
||||
| 8 | #59 | dream: event log replay | ~30 |
|
||||
| 9 | #59 | dream: パターン抽出 (GATE 拒否頻度、ロック競合) | ~40 |
|
||||
| 10 | #59 | dream: 学び重要度判定 (High/Medium/Low) | ~20 |
|
||||
| 11 | #59 | dream: High → docs/ にコミット | ~20 |
|
||||
| 12 | #59 | dream: Medium → Issue コメントに追記 | ~15 |
|
||||
| 13 | #59 | dream: Low → アーカイブ | ~10 |
|
||||
| 14 | #59 | CLI: miyabi gate dream [--since 24h] [--auto] | ~20 |
|
||||
| 15 | #59 | launchd: 毎晩 03:00 に dream --auto 実行 | plist |
|
||||
| 16 | #63 | Web ダッシュボード: miyabi gate serve (localhost:4848) | ~100 |
|
||||
| 17 | — | Heartbeat デーモン (launchd 60秒) | ~50 |
|
||||
| 18 | — | Telegram 通知 (GATE 通過/拒否) | ~50 |
|
||||
| 19 | — | VOICEBOX 自動化 (hooks.yaml) | ~20 |
|
||||
| 20 | — | git 自動同期 (merge 後に auto push) | ~30 |
|
||||
| 21 | — | Maestro Playbook 登録 | 設定 |
|
||||
|
||||
**GATE**: attach_context が動作 + dream が docs/ に昇格 + Web ダッシュボード表示
|
||||
**タグ**: `v1.2-memory-attached`
|
||||
|
||||
---
|
||||
|
||||
## Sprint 4: シータサイクル + Obsidian + 品質ゲート(今月)
|
||||
|
||||
> ビジョン対応: 原則 1 を 70% → 100% + 自己改善 0% → 100%
|
||||
|
||||
| # | Issue | タスク | 行数 |
|
||||
|---|-------|--------|------|
|
||||
| 1 | #60 | シータ θ1: dream に Knowledge Watcher 差分検知を統合 | ~30 |
|
||||
| 2 | #60 | シータ θ5: skill-bus record-run を dream 内で自動呼出 | ~10 |
|
||||
| 3 | #60 | シータ θ6: Self-Improving → SKILL.md 自動更新 | ~30 |
|
||||
| 4 | #60 | シータ θ6: attach_context の選別ルール自動改善 | ~20 |
|
||||
| 5 | #62 | Obsidian: dream → Vault ノート生成 | ~50 |
|
||||
| 6 | #62 | Obsidian: wikilink 自動生成 | ~30 |
|
||||
| 7 | #62 | Obsidian: MOC 自動更新 | ~20 |
|
||||
| 8 | #62 | Obsidian: Smart Connections 埋め込み再生成トリガー | ~10 |
|
||||
| 9 | #62 | attach_context: Smart Connections セマンティック検索追加 | ~30 |
|
||||
| 10 | #62 | attach_context: wikilink 追跡で関連ノート展開 | ~20 |
|
||||
| 11 | — | rust-ai-pipeline Phase 1 統合 (GATE 4.5) | ~50 |
|
||||
| 12 | — | proptest 拡張 (gate/lock/store) | ~100 |
|
||||
| 13 | — | cargo-mutants (mutation score 80%+) | 設定 |
|
||||
|
||||
**GATE**: シータが自律巡回 + Obsidian にノート生成 + mutation 80%+
|
||||
**タグ**: `v1.3-theta-obsidian`
|
||||
|
||||
---
|
||||
|
||||
## Sprint 5: 移行 + 公開(来月以降)
|
||||
|
||||
| # | タスク |
|
||||
|---|--------|
|
||||
| 1 | miyabi-private (TS) → Rust 段階的移行 |
|
||||
| 2 | OpenClaw プラグインとして公開 |
|
||||
| 3 | npm パッケージ配布 |
|
||||
|
||||
**タグ**: `v2.0-public`
|
||||
|
||||
---
|
||||
|
||||
## Sprint DAG
|
||||
|
||||
```
|
||||
Sprint 1 (#52-57 + Phase C) ← 今日。GATE を固める
|
||||
│
|
||||
▼
|
||||
Sprint 2 (#61,64,65) ← 今週。Bus接続 + ブランチ戦略
|
||||
│
|
||||
▼
|
||||
Sprint 3 (#58,59,63) ← 来週。★記憶アタッチメント + ドリーミング★
|
||||
│
|
||||
▼
|
||||
Sprint 4 (#60,62) ← 今月。シータ + Obsidian + 品質ゲート
|
||||
│
|
||||
▼
|
||||
Sprint 5 ← 来月。公開
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ビジョン達成度の推移
|
||||
|
||||
```
|
||||
原則1 原則2 原則3 可視化 自己改善 全体
|
||||
現在: 0% 58% 44% 13% 0% 32%
|
||||
Sprint1後: 0% 90% 50% 13% 0% 38%
|
||||
Sprint2後: 0% 95% 80% 13% 0% 47%
|
||||
Sprint3後: 70% 95% 85% 60% 10% 72%
|
||||
Sprint4後: 100% 100% 90% 70% 100% 93%
|
||||
Sprint5後: 100% 100% 100% 80% 100% 97%
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 全 Issue → Sprint マッピング
|
||||
|
||||
| Issue | Sprint | 原則 |
|
||||
|-------|--------|------|
|
||||
| #52 GATE 0 Issue=0 | 1 | 原則2 |
|
||||
| #53 GATE 5 ブランチ名 | 1 | 原則2 |
|
||||
| #54 GATE 3 HIGH承認 | 1 | 原則2 |
|
||||
| #55 GATE 7 ロック解放 | 1 | 原則2 |
|
||||
| #56 exit code | 1 | 原則2 |
|
||||
| #57 依存 exit code | 1 | 原則2 |
|
||||
| #58 記憶アタッチメント | 3 | **原則1** |
|
||||
| #59 ドリーミング | 3 | **原則1** |
|
||||
| #60 シータサイクル | 4 | 自己改善 |
|
||||
| #61 Bus ドッキング | 2 | 原則3 |
|
||||
| #62 Obsidian 連携 | 4 | **原則1** |
|
||||
| #63 Web ダッシュボード | 3 | 可視化 |
|
||||
| #64 ブランチ戦略 | 2 | 原則2 |
|
||||
| #65 Bus データパス | 2 | 原則3 |
|
||||
|
||||
---
|
||||
|
||||
_This is Playbook v4. It covers all 38 elements of the VISION._
|
||||
_Previous versions (v1-v3) are archived in docs/dtp/._
|
||||
|
|
@ -2,3 +2,6 @@
|
|||
{"ts":"2026-04-09T21:17:09.658Z","agent":"codex","skill":"context-and-impact","task":"DTP Phase B: miyabi gate CLI サブコマンド追加、protocol API 拡張、cargo test とコミット完了","result":"success","score":0.96,"notes":""}
|
||||
{"ts":"2026-04-09T21:27:55.420Z","agent":"codex","skill":"context-and-impact","task":"miyabi-core の clippy 7件を解消し clippy/test を通過確認。ただし HEAD 差分ゼロのため指定コミットは未作成","result":"partial","score":0.78,"notes":""}
|
||||
{"ts":"2026-04-09T21:28:53.022Z","agent":"codex","skill":"context-and-impact","task":"clippy 7件解消の確認後、空コミットで指定メッセージを作成","result":"success","score":0.92,"notes":""}
|
||||
{"ts":"2026-04-09T21:49:02.984Z","agent":"codex","skill":"rust-development","task":"Fix GitHub Issues #56 and #57: gate exit code mapping and dependency-blocked assign behavior","result":"success","score":0.97,"notes":""}
|
||||
{"ts":"2026-04-09T21:50:24.425Z","agent":"codex","skill":"rust-llm-pitfalls","task":"Fix GitHub Issues #54 and #55 in protocol.rs with approval and merge lock handling","result":"success","score":1,"notes":""}
|
||||
{"ts":"2026-04-09T21:52:05.485Z","agent":"codex","skill":"rust-llm-pitfalls","task":"Issue #52/#53 修正: issue=0 拒否とブランチ名バリデーション実装","result":"success","score":0.95,"notes":""}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue