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:
liuzh
2026-04-01 19:22:04 +08:00
co-authored by Copilot
parent 2a565d314d
commit d67f41358d
33 changed files with 2456 additions and 0 deletions
@@ -0,0 +1,79 @@
package com.claudecode.console;
import java.io.PrintStream;
/**
* Markdown 简易渲染器 —— 对应 claude-code/src/renderers/markdown.ts。
* <p>
* 将 AI 回复中的 Markdown 格式转换为终端 ANSI 样式输出。
* 这是一个简化版,支持常见格式。
*/
public class MarkdownRenderer {
private final PrintStream out;
public MarkdownRenderer(PrintStream out) {
this.out = out;
}
/** 渲染 Markdown 文本 */
public void render(String markdown) {
if (markdown == null || markdown.isBlank()) return;
boolean inCodeBlock = false;
String codeBlockLang = "";
for (String line : markdown.lines().toList()) {
// 代码块
if (line.stripLeading().startsWith("```")) {
if (!inCodeBlock) {
codeBlockLang = line.stripLeading().substring(3).strip();
inCodeBlock = true;
out.println(AnsiStyle.dim(" ┌─" + (codeBlockLang.isEmpty() ? "code" : codeBlockLang) + ""));
continue;
} else {
inCodeBlock = false;
out.println(AnsiStyle.dim(" └─────"));
continue;
}
}
if (inCodeBlock) {
out.println(AnsiStyle.BRIGHT_GREEN + "" + line + AnsiStyle.RESET);
continue;
}
// 标题
if (line.startsWith("### ")) {
out.println(AnsiStyle.bold(AnsiStyle.CYAN + " " + line.substring(4)) + AnsiStyle.RESET);
} else if (line.startsWith("## ")) {
out.println(AnsiStyle.bold(AnsiStyle.BLUE + " " + line.substring(3)) + AnsiStyle.RESET);
} else if (line.startsWith("# ")) {
out.println(AnsiStyle.bold(AnsiStyle.MAGENTA + " " + line.substring(2)) + AnsiStyle.RESET);
}
// 列表项
else if (line.stripLeading().startsWith("- ") || line.stripLeading().startsWith("* ")) {
out.println(" " + AnsiStyle.CYAN + "" + AnsiStyle.RESET + " " + renderInline(line.stripLeading().substring(2)));
}
// 分隔线
else if (line.strip().matches("^-{3,}$") || line.strip().matches("^\\*{3,}$")) {
out.println(AnsiStyle.dim(" ─────────────────────────────────────────"));
}
// 普通文本
else {
out.println(" " + renderInline(line));
}
}
}
/** 行内格式渲染 */
private String renderInline(String text) {
// 粗体 **text**
text = text.replaceAll("\\*\\*(.+?)\\*\\*", AnsiStyle.BOLD + "$1" + AnsiStyle.RESET);
// 行内代码 `text`
text = text.replaceAll("`(.+?)`", AnsiStyle.BRIGHT_GREEN + "$1" + AnsiStyle.RESET);
// 斜体 *text*
text = text.replaceAll("\\*(.+?)\\*", AnsiStyle.ITALIC + "$1" + AnsiStyle.RESET);
return text;
}
}