feat: Phase1 项目骨架 - Maven项目结构、核心AgentLoop、6个工具、命令系统、控制台渲染、REPL会话
实现内容: - pom.xml: JDK25 + Spring AI 2.0.0-M4 + JLine3 + Picocli - core/AgentLoop: 基于ChatModel的显式工具循环(非ChatClient) - tool/: Tool接口 + ToolRegistry + ToolCallbackAdapter(适配Spring AI) - tool/impl/: BashTool, FileReadTool, FileWriteTool, FileEditTool, GlobTool, GrepTool - command/: SlashCommand接口 + CommandRegistry + /help, /clear, /exit - console/: AnsiStyle, BannerPrinter, ToolStatusRenderer, ThinkingRenderer, SpinnerAnimation, MarkdownRenderer - context/: SystemPromptBuilder, ClaudeMdLoader(多级CLAUDE.md加载) - repl/ReplSession: REPL主循环(Scanner降级方案) - config/AppConfig: Spring Bean装配 - application.yml: Anthropic/OpenAI双模型配置 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
package com.claudecode.context;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CLAUDE.md 加载器 —— 对应 claude-code/src/context.ts 中的 CLAUDE.md 加载逻辑。
|
||||
* <p>
|
||||
* 按优先级从低到高加载:
|
||||
* <ol>
|
||||
* <li>系统级: /etc/claude-code/CLAUDE.md (Unix) 或默认模板</li>
|
||||
* <li>用户级: ~/.claude/CLAUDE.md</li>
|
||||
* <li>项目级: ./CLAUDE.md 或 ./.claude/CLAUDE.md</li>
|
||||
* <li>本地级: ./CLAUDE.local.md</li>
|
||||
* </ol>
|
||||
*/
|
||||
public class ClaudeMdLoader {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ClaudeMdLoader.class);
|
||||
|
||||
private final Path projectDir;
|
||||
|
||||
public ClaudeMdLoader(Path projectDir) {
|
||||
this.projectDir = projectDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载并合并所有 CLAUDE.md 内容。
|
||||
*/
|
||||
public String load() {
|
||||
List<String> sections = new ArrayList<>();
|
||||
|
||||
// 1. 用户级
|
||||
Path userMd = Path.of(System.getProperty("user.home"), ".claude", "CLAUDE.md");
|
||||
loadFile(userMd, "user").ifPresent(sections::add);
|
||||
|
||||
// 2. 项目级 —— 优先检查 .claude/CLAUDE.md,然后 CLAUDE.md
|
||||
Path projectClaudeDir = projectDir.resolve(".claude").resolve("CLAUDE.md");
|
||||
Path projectRoot = projectDir.resolve("CLAUDE.md");
|
||||
if (Files.exists(projectClaudeDir)) {
|
||||
loadFile(projectClaudeDir, "project").ifPresent(sections::add);
|
||||
} else {
|
||||
loadFile(projectRoot, "project").ifPresent(sections::add);
|
||||
}
|
||||
|
||||
// 3. 本地级
|
||||
Path localMd = projectDir.resolve("CLAUDE.local.md");
|
||||
loadFile(localMd, "local").ifPresent(sections::add);
|
||||
|
||||
// 4. 加载 .claude/rules/*.md 目录
|
||||
Path rulesDir = projectDir.resolve(".claude").resolve("rules");
|
||||
if (Files.isDirectory(rulesDir)) {
|
||||
try (var stream = Files.list(rulesDir)) {
|
||||
stream.filter(p -> p.toString().endsWith(".md"))
|
||||
.sorted()
|
||||
.forEach(p -> loadFile(p, "rule").ifPresent(sections::add));
|
||||
} catch (IOException e) {
|
||||
log.debug("加载规则目录失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (sections.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return String.join("\n\n---\n\n", sections);
|
||||
}
|
||||
|
||||
private java.util.Optional<String> loadFile(Path path, String level) {
|
||||
if (!Files.exists(path) || !Files.isRegularFile(path)) {
|
||||
return java.util.Optional.empty();
|
||||
}
|
||||
try {
|
||||
String content = Files.readString(path, StandardCharsets.UTF_8).strip();
|
||||
if (!content.isEmpty()) {
|
||||
log.debug("已加载 {} 级 CLAUDE.md: {}", level, path);
|
||||
return java.util.Optional.of(content);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn("读取 {} 失败: {}", path, e.getMessage());
|
||||
}
|
||||
return java.util.Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.claudecode.context;
|
||||
|
||||
/**
|
||||
* 系统提示词构建器 —— 对应 claude-code/src/prompts.ts。
|
||||
* <p>
|
||||
* 组装完整的系统提示词,包括核心指令、环境信息、工具说明等。
|
||||
*/
|
||||
public class SystemPromptBuilder {
|
||||
|
||||
private String workDir;
|
||||
private String osName;
|
||||
private String userName;
|
||||
private String claudeMdContent;
|
||||
private String customInstructions;
|
||||
|
||||
public SystemPromptBuilder() {
|
||||
this.workDir = System.getProperty("user.dir");
|
||||
this.osName = System.getProperty("os.name");
|
||||
this.userName = System.getProperty("user.name");
|
||||
}
|
||||
|
||||
public SystemPromptBuilder workDir(String workDir) {
|
||||
this.workDir = workDir;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SystemPromptBuilder claudeMd(String content) {
|
||||
this.claudeMdContent = content;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SystemPromptBuilder customInstructions(String instructions) {
|
||||
this.customInstructions = instructions;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建完整的系统提示词。
|
||||
*/
|
||||
public String build() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// 核心角色定义
|
||||
sb.append("""
|
||||
You are Claude, an AI assistant made by Anthropic, operating as a CLI coding agent.
|
||||
You are an interactive CLI tool that helps users with software engineering tasks.
|
||||
Use the provided tools to help the user with their request.
|
||||
|
||||
""");
|
||||
|
||||
// 环境信息
|
||||
sb.append("# Environment\n");
|
||||
sb.append("- Working directory: ").append(workDir).append("\n");
|
||||
sb.append("- OS: ").append(osName).append("\n");
|
||||
sb.append("- User: ").append(userName).append("\n");
|
||||
sb.append("\n");
|
||||
|
||||
// 行为准则
|
||||
sb.append("""
|
||||
# Guidelines
|
||||
- Be concise in responses, but thorough in implementation
|
||||
- Always verify changes work before considering a task done
|
||||
- Use tools to explore the codebase before making changes
|
||||
- When writing code, follow existing patterns and conventions
|
||||
- Ask for clarification when requirements are ambiguous
|
||||
|
||||
""");
|
||||
|
||||
// CLAUDE.md 内容
|
||||
if (claudeMdContent != null && !claudeMdContent.isBlank()) {
|
||||
sb.append("# Project Instructions (CLAUDE.md)\n");
|
||||
sb.append(claudeMdContent).append("\n\n");
|
||||
}
|
||||
|
||||
// 自定义指令
|
||||
if (customInstructions != null && !customInstructions.isBlank()) {
|
||||
sb.append("# Custom Instructions\n");
|
||||
sb.append(customInstructions).append("\n\n");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user