- EnterPlanModeTool: switch to read-only mode with plan file path management - ExitPlanModeTool: restore permissions and validate plan content - PermissionRuleEngine: plan file edit exception in PLAN mode - SystemPromptBuilder: 5-phase plan workflow instructions injection - /plan command: toggle plan mode from command line - Plan file stored at ~/.claude/projects/[path]/PLAN.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>pull/1/head
parent
bd98dea6b3
commit
cb76b267f1
@ -0,0 +1,48 @@ |
|||||||
|
package com.claudecode.command.impl; |
||||||
|
|
||||||
|
import com.claudecode.command.CommandContext; |
||||||
|
import com.claudecode.command.SlashCommand; |
||||||
|
import com.claudecode.permission.PermissionSettings; |
||||||
|
import com.claudecode.permission.PermissionTypes.PermissionMode; |
||||||
|
|
||||||
|
/** |
||||||
|
* /plan 命令 —— 对应 claude-code/src/commands/plan/plan.tsx。 |
||||||
|
* <p> |
||||||
|
* 切换计划模式开关。在计划模式下,AI只能分析不能修改。 |
||||||
|
*/ |
||||||
|
public class PlanCommand implements SlashCommand { |
||||||
|
|
||||||
|
private final PermissionSettings permissionSettings; |
||||||
|
|
||||||
|
public PlanCommand(PermissionSettings permissionSettings) { |
||||||
|
this.permissionSettings = permissionSettings; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String name() { |
||||||
|
return "plan"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String description() { |
||||||
|
return "Toggle plan mode (analysis only, no file modifications)"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String execute(String args, CommandContext context) { |
||||||
|
PermissionMode currentMode = permissionSettings.getCurrentMode(); |
||||||
|
|
||||||
|
if (currentMode == PermissionMode.PLAN) { |
||||||
|
// Exit plan mode
|
||||||
|
permissionSettings.setCurrentMode(PermissionMode.DEFAULT); |
||||||
|
return "📋 Exited plan mode. Normal permissions restored.\n" + |
||||||
|
"All tools are now available."; |
||||||
|
} else { |
||||||
|
// Enter plan mode
|
||||||
|
permissionSettings.setCurrentMode(PermissionMode.PLAN); |
||||||
|
return "📋 Entered plan mode.\n" + |
||||||
|
"Only read-only tools are available. Use /plan again to exit.\n" + |
||||||
|
"Or ask the AI to call EnterPlanMode for the full workflow."; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,150 @@ |
|||||||
|
package com.claudecode.tool.impl; |
||||||
|
|
||||||
|
import com.claudecode.permission.PermissionSettings; |
||||||
|
import com.claudecode.permission.PermissionTypes.PermissionMode; |
||||||
|
import com.claudecode.tool.ToolContext; |
||||||
|
import com.claudecode.tool.Tool; |
||||||
|
|
||||||
|
import java.nio.file.Files; |
||||||
|
import java.nio.file.Path; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* 进入计划模式工具 —— 对应 claude-code/src/tools/EnterPlanModeTool。 |
||||||
|
* <p> |
||||||
|
* 将权限切换到只读模式,AI只能分析代码不能修改, |
||||||
|
* 只有计划文件(PLAN.md)可以编辑。 |
||||||
|
*/ |
||||||
|
public class EnterPlanModeTool implements Tool { |
||||||
|
|
||||||
|
public static final String PLAN_MODE_KEY = "PLAN_MODE_ACTIVE"; |
||||||
|
public static final String PLAN_FILE_PATH_KEY = "PLAN_FILE_PATH"; |
||||||
|
public static final String PRE_PLAN_MODE_KEY = "PRE_PLAN_MODE"; |
||||||
|
|
||||||
|
@Override |
||||||
|
public String name() { |
||||||
|
return "EnterPlanMode"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String description() { |
||||||
|
return """ |
||||||
|
Enter plan mode to analyze the codebase and design an implementation plan WITHOUT making changes. |
||||||
|
|
||||||
|
When to use: |
||||||
|
- User asks you to "plan" or "think about" a change before implementing |
||||||
|
- User wants to understand approach before committing to it |
||||||
|
- Complex multi-file changes that need careful design |
||||||
|
|
||||||
|
In plan mode: |
||||||
|
- You can ONLY use read-only tools (Read, Grep, Glob, ListFiles, WebFetch, WebSearch) |
||||||
|
- You can ONLY write to the plan file (PLAN.md) |
||||||
|
- All other file modifications and shell commands are BLOCKED |
||||||
|
- Use AskUserQuestion to clarify requirements |
||||||
|
- Call ExitPlanMode when the plan is complete |
||||||
|
|
||||||
|
The plan file location is determined automatically based on the project path. |
||||||
|
"""; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String inputSchema() { |
||||||
|
return """ |
||||||
|
{ |
||||||
|
"type": "object", |
||||||
|
"properties": { |
||||||
|
"reason": { |
||||||
|
"type": "string", |
||||||
|
"description": "Brief reason for entering plan mode" |
||||||
|
} |
||||||
|
}, |
||||||
|
"required": [] |
||||||
|
} |
||||||
|
"""; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String execute(Map<String, Object> input, ToolContext context) { |
||||||
|
// Check if already in plan mode
|
||||||
|
Boolean active = context.getOrDefault(PLAN_MODE_KEY, false); |
||||||
|
if (active) { |
||||||
|
String existingPlan = context.get(PLAN_FILE_PATH_KEY); |
||||||
|
return "Already in plan mode. Plan file: " + existingPlan; |
||||||
|
} |
||||||
|
|
||||||
|
// Determine plan file path
|
||||||
|
Path workDir = context.getWorkDir(); |
||||||
|
Path planDir = getPlanDirectory(workDir); |
||||||
|
Path planFile = planDir.resolve("PLAN.md"); |
||||||
|
|
||||||
|
// Save pre-plan mode for restoration
|
||||||
|
PermissionSettings permSettings = context.get("PERMISSION_SETTINGS"); |
||||||
|
if (permSettings != null) { |
||||||
|
PermissionMode previousMode = permSettings.getCurrentMode(); |
||||||
|
context.set(PRE_PLAN_MODE_KEY, previousMode); |
||||||
|
// Switch to PLAN mode
|
||||||
|
permSettings.setCurrentMode(PermissionMode.PLAN); |
||||||
|
} |
||||||
|
|
||||||
|
// Store plan state
|
||||||
|
context.set(PLAN_MODE_KEY, true); |
||||||
|
context.set(PLAN_FILE_PATH_KEY, planFile.toString()); |
||||||
|
|
||||||
|
// Create plan directory if needed
|
||||||
|
try { |
||||||
|
Files.createDirectories(planDir); |
||||||
|
} catch (Exception e) { |
||||||
|
// Non-fatal
|
||||||
|
} |
||||||
|
|
||||||
|
String reason = input != null ? (String) input.get("reason") : null; |
||||||
|
boolean planExists = Files.exists(planFile); |
||||||
|
|
||||||
|
StringBuilder result = new StringBuilder(); |
||||||
|
result.append("✅ Entered plan mode.\n\n"); |
||||||
|
result.append("📋 Plan file: ").append(planFile).append("\n"); |
||||||
|
if (planExists) { |
||||||
|
result.append("📄 Existing plan found — you can read and update it.\n"); |
||||||
|
} else { |
||||||
|
result.append("📝 No existing plan — create one by writing to the plan file.\n"); |
||||||
|
} |
||||||
|
result.append("\n"); |
||||||
|
result.append("Restrictions active:\n"); |
||||||
|
result.append(" • Only read-only tools allowed (Read, Grep, Glob, etc.)\n"); |
||||||
|
result.append(" • Only the plan file can be edited\n"); |
||||||
|
result.append(" • Shell commands are blocked\n"); |
||||||
|
result.append(" • Call ExitPlanMode when your plan is ready\n"); |
||||||
|
|
||||||
|
if (reason != null && !reason.isBlank()) { |
||||||
|
result.append("\nReason: ").append(reason); |
||||||
|
} |
||||||
|
|
||||||
|
return result.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isReadOnly() { |
||||||
|
// This tool itself doesn't modify files
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String activityDescription(Map<String, Object> input) { |
||||||
|
return "Entering plan mode..."; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Get plan directory for the given work directory. |
||||||
|
* Uses ~/.claude/projects/[sanitized-path]/ structure. |
||||||
|
*/ |
||||||
|
static Path getPlanDirectory(Path workDir) { |
||||||
|
String sanitized = workDir.toAbsolutePath().toString() |
||||||
|
.replace(":", "_") |
||||||
|
.replace("\\", "_") |
||||||
|
.replace("/", "_"); |
||||||
|
return Path.of(System.getProperty("user.home")) |
||||||
|
.resolve(".claude") |
||||||
|
.resolve("projects") |
||||||
|
.resolve(sanitized); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue