Add Plan Mode to replace TODO system
Plan Mode is a cognitive forcing system that requires reasoning about: - Happy path - Negative case - Boundary condition New tools: - plan_read: Read current plan for session - plan_write: Create/update plan with YAML content (validates structure) - plan_approve: Mark current revision as approved New command: - /feature <description>: Start Plan Mode for a new feature Plan schema requires: - plan_id, revision, approved_revision - items with id, description, state, touches, checks (happy/negative/boundary) - evidence and notes required when marking items done Verification: - plan_verify() called automatically when all items are done/blocked Removed: - todo_read, todo_write tools - todo.rs module and related tests
This commit is contained in:
@@ -193,29 +193,6 @@ fn create_core_tools(exclude_research: bool) -> Vec<Tool> {
|
||||
"required": ["path", "window_id"]
|
||||
}),
|
||||
},
|
||||
Tool {
|
||||
name: "todo_read".to_string(),
|
||||
description: "Read your current TODO list from todo.g3.md file in the session directory. Shows what tasks are planned and their status. Call this at the start of multi-step tasks to check for existing plans, and during execution to review progress before updating. TODO lists are scoped to the current session.".to_string(),
|
||||
input_schema: json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}),
|
||||
},
|
||||
Tool {
|
||||
name: "todo_write".to_string(),
|
||||
description: "Create or update your TODO list in todo.g3.md file with a complete task plan. Use markdown checkboxes: - [ ] for pending, - [x] for complete. This tool replaces the entire file content, so always call todo_read first to preserve existing content. Essential for multi-step tasks. TODO lists are scoped to the current session.".to_string(),
|
||||
input_schema: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "The TODO list content to save. Use markdown checkbox format: - [ ] for incomplete tasks, - [x] for completed tasks. Support nested tasks with indentation."
|
||||
}
|
||||
},
|
||||
"required": ["content"]
|
||||
}),
|
||||
},
|
||||
Tool {
|
||||
name: "coverage".to_string(),
|
||||
description: "Generate a code coverage report for the entire workspace using cargo llvm-cov. This runs all tests with coverage instrumentation and returns a summary of coverage statistics. Requires llvm-tools-preview and cargo-llvm-cov to be installed (they will be auto-installed if missing).".to_string(),
|
||||
@@ -288,6 +265,62 @@ fn create_core_tools(exclude_research: bool) -> Vec<Tool> {
|
||||
});
|
||||
}
|
||||
|
||||
// Plan Mode tools
|
||||
tools.push(Tool {
|
||||
name: "plan_read".to_string(),
|
||||
description: "Read the current Plan for this session. Shows the plan structure with items, their states, checks (happy/negative/boundary), evidence, and notes. Use this to review the plan before making updates.".to_string(),
|
||||
input_schema: json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}),
|
||||
});
|
||||
|
||||
tools.push(Tool {
|
||||
name: "plan_write".to_string(),
|
||||
description: r#"Create or update the Plan for this session. The plan must be provided as YAML with the following structure:
|
||||
|
||||
- plan_id: Unique identifier for the plan
|
||||
- revision: Will be auto-incremented
|
||||
- items: Array of plan items, each with:
|
||||
- id: Stable identifier (e.g., "I1")
|
||||
- description: What will be done
|
||||
- state: todo | doing | done | blocked
|
||||
- touches: Array of paths/modules affected
|
||||
- checks:
|
||||
happy: {desc, target} - Normal successful operation
|
||||
negative: {desc, target} - Error handling, invalid input
|
||||
boundary: {desc, target} - Edge cases, limits
|
||||
- evidence: Array of file:line refs, test names (required when done)
|
||||
- notes: Implementation explanation (required when done)
|
||||
|
||||
Rules:
|
||||
- Keep items ≤ 7 by default
|
||||
- All three checks (happy, negative, boundary) are required
|
||||
- Cannot remove items from an approved plan (mark as blocked instead)
|
||||
- Evidence and notes required when marking item as done"#.to_string(),
|
||||
input_schema: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"plan": {
|
||||
"type": "string",
|
||||
"description": "The plan as YAML. Must include plan_id and items array."
|
||||
}
|
||||
},
|
||||
"required": ["plan"]
|
||||
}),
|
||||
});
|
||||
|
||||
tools.push(Tool {
|
||||
name: "plan_approve".to_string(),
|
||||
description: "Mark the current plan revision as approved. This is called by the user (not the agent) to approve a drafted plan before implementation begins. Once approved, plan items cannot be removed (only marked as blocked). The agent should ask for approval after drafting a plan.".to_string(),
|
||||
input_schema: json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}),
|
||||
});
|
||||
|
||||
// Workspace memory tool (memory is auto-loaded at startup, only remember is needed)
|
||||
tools.push(Tool {
|
||||
name: "remember".to_string(),
|
||||
@@ -523,11 +556,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_core_tools_count() {
|
||||
let tools = create_core_tools(false);
|
||||
// Should have the core tools: shell, background_process, read_file, read_image,
|
||||
// write_file, str_replace, screenshot,
|
||||
// todo_read, todo_write, coverage, code_search, research, research_status, remember
|
||||
// (15 total - memory is auto-loaded, only remember tool needed)
|
||||
assert_eq!(tools.len(), 15);
|
||||
// Core tools: shell, background_process, read_file, read_image,
|
||||
// write_file, str_replace, screenshot, coverage, code_search,
|
||||
// research, research_status, remember, plan_read, plan_write, plan_approve
|
||||
// (16 total - memory is auto-loaded, only remember tool needed)
|
||||
assert_eq!(tools.len(), 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -541,15 +574,15 @@ mod tests {
|
||||
fn test_create_tool_definitions_core_only() {
|
||||
let config = ToolConfig::default();
|
||||
let tools = create_tool_definitions(config);
|
||||
assert_eq!(tools.len(), 15);
|
||||
assert_eq!(tools.len(), 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_tool_definitions_all_enabled() {
|
||||
let config = ToolConfig::new(true, true);
|
||||
let tools = create_tool_definitions(config);
|
||||
// 15 core + 15 webdriver = 30
|
||||
assert_eq!(tools.len(), 30);
|
||||
// 16 core + 15 webdriver = 31
|
||||
assert_eq!(tools.len(), 31);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -567,8 +600,8 @@ mod tests {
|
||||
let tools_with_research = create_core_tools(false);
|
||||
let tools_without_research = create_core_tools(true);
|
||||
|
||||
assert_eq!(tools_with_research.len(), 15);
|
||||
assert_eq!(tools_without_research.len(), 13); // research + research_status both excluded
|
||||
assert_eq!(tools_with_research.len(), 16);
|
||||
assert_eq!(tools_without_research.len(), 14); // research + research_status both excluded
|
||||
|
||||
assert!(tools_with_research.iter().any(|t| t.name == "research"));
|
||||
assert!(!tools_without_research.iter().any(|t| t.name == "research"));
|
||||
|
||||
Reference in New Issue
Block a user