diff --git a/.gitignore b/.gitignore index 89d64fa..99f6ff5 100644 --- a/.gitignore +++ b/.gitignore @@ -73,10 +73,8 @@ testdata/repos testdata/jsons src/lang/testdata -*.json !ts-parser/**/*.json tools -abcoder !testdata/asts/*.json \ No newline at end of file diff --git a/README.md b/README.md index f83c8e6..cc6a62f 100644 --- a/README.md +++ b/README.md @@ -25,101 +25,40 @@ see [UniAST Specification](docs/uniast-zh.md) # Quick Start -## Use ABCoder as a MCP server - -1. Install ABCoder: - - ```bash - go install github.com/cloudwego/abcoder@latest - ``` - -2. Use ABCoder to parse a repository to UniAST (JSON) - - ```bash - abcoder parse {language} {repo-path} -o xxx.json - ``` - - ABCoder will try to install any dependency automatically. - In case of failure (or if you want to customize installation), refer to the [docs](./docs/lsp-installation-en.md). - - For example, to parse a Go repository: - - ```bash - git clone https://github.com/cloudwego/localsession.git localsession - abcoder parse go localsession -o /abcoder-asts/localsession.json - ``` - - -3. Integrate ABCoder's MCP tools into your AI agent. - - ```json - { - "mcpServers": { - "abcoder": { - "command": "abcoder", - "args": [ - "mcp", - "{the-AST-directory}" - ] - } - } - } - ``` - - -4. Enjoy it! - - Try to click and watch the video below: - -
- - [MCP](https://www.bilibili.com/video/BV14ggJzCEnK) - -
- - -## Tips: - -- You can add more repo ASTs into the AST directory without restarting abcoder MCP server. - -- Try to use [the recommended prompt](llm/prompt/analyzer.md) and combine planning/memory tools like [sequential-thinking](https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking) in your AI agent. - ## Claude Code Integration -ABCoder provides deep integration with [Claude Code](https://claude.ai/code) through the AST-Driven Coding workflow, enabling hallucination-free code analysis and precise execution. +ABCoder provides deep integration with [Claude Code](https://claude.ai/code) through the AST-Driven Coding workflow, enabling hallucination-free code analysis and precise execution. Check [Claude Code Specification](docs/claude-code-spec.md) for more details. ### Setup -1. **Install ABCoder Claude Configuration** - - Copy [`docs/.claude/`](docs/.claude/) to your home directory or project root: +Use the `init-spec` command to automatically configure Claude Code integration for your project: - ```bash - cp -r docs/.claude ~/ - ``` +```bash +# Install ABCoder +go install github.com/cloudwego/abcoder@latest -2. **Configure ABCoder MCP Server** +# Run init-spec in your project directory (optional: specify target path) +cd /path/to/your/project +abcoder init-spec +``` - Configure in Claude Code's `~/.claude.json` (the hook uses `abcoder parse go/ts . -o ~/.asts/repo.json` for the default AST folder): +The `init-spec` command will: +1. Copy `.claude` directory to your project root +2. Configure MCP servers in `~/.claude.json`: + - `abcoder`: for code analysis using AST + - `sequential-thinking`: for complex problem decomposition +3. Replace all `{{CLAUDE_HOME_PATH}}` placeholders with actual project paths - ```json - { - "mcpServers": { - "abcoder": { - "command": "abcoder", - "args": ["mcp", "~/.asts"] - } - } - } - ``` +### Start Coding with Claude Code -3. **Configure Hooks** +Once setup, you can start coding with Claude Code: - Claude Code will automatically read hooks from [`~/.claude/settings.json`](docs/.claude/settings.json) to enable: - - Auto-detect language and generate AST before calling `mcp__abcoder` tools - - Display ABCoder workflow SOP to Claude after `list_repos` - - Remind to call `get_ast_node` recursively +1. Start Claude Code in your project directory +2. Use slash common `/abcoder:schedule ` to address your feature/requirement/issue, and ABCoder will help you analyze the codebase and design a technical solution. +3. Once all questions are set, use slash common `/abcoder:task ` to create a coding task(specification) +4. Recheck the task using `/abcoder:recheck ` before real implementation +5. Begin coding! Claude Code will process the task step by step according to the specification, leveraging the power of AST-driven analysis. ### AST-Driven Coding Workflow @@ -137,7 +76,7 @@ list_repos → get_repo_structure → get_package_structure → get_file_structu | Command | Function | Description | |---------|----------|-------------| -| [`/abcoder:schd`](docs/.claude/commands/abcoder:schd.md) | Design implementation | Analyze codebase using ABCoder, design technical solution | +| [`/abcoder:schedule` ](docs/.claude/commands/abcoder:schedule.md) | Design implementation | Analyze codebase by using ABCoder, design technical solution | | [`/abcoder:task `](docs/.claude/commands/abcoder:task.md) | Create coding task | Generate standardized CODE_TASK document | | [`/abcoder:recheck `](docs/.claude/commands/abcoder:recheck.md) | Verify solution | Critically check CODE_TASK feasibility, useful when a CODE_TASK contains external dependencies | @@ -147,7 +86,7 @@ list_repos → get_repo_structure → get_package_structure → get_file_structu User Request │ ▼ -/abcoder:schd ──────────→ Design Solution (ABCoder Analysis) +/abcoder:schedule ──────────→ Design Solution (ABCoder Analysis) │ │ ▼ ▼ /abcoder:task ─────────→ CODE_TASK (with Technical Specs, including accurate `get_ast_node` call args) @@ -156,7 +95,7 @@ User Request /abcoder:recheck ────→ Verify Solution (ABCoder Validation. After `/abcoder:task` Claude Code will tell you what the external dependencies CODE_TASK contains, use `/abcoder:recheck` to analyze external ast_node and technical detail with ABCoder) │ │ ▼ ▼ -sub-agent ─────────→ Execute Implementation +Start coding(sub-agent) ─────────→ Execute Implementation ``` ### Configuration Files @@ -166,19 +105,78 @@ sub-agent ─────────→ Execute Implementation | [`CLAUDE.md`](docs/.claude/CLAUDE.md) | Core AST-Driven Coder role definition | | [`settings.json`](docs/.claude/settings.json) | Hooks and permissions configuration | | [`hooks/`](docs/.claude/hooks/) | Automation scripts (parse/prompt/reminder) | -| [`commands/`](docs/.claude/commands/) | Slash command definitions (abcoder:task/abcoder:schd/abcoder:recheck) | -| [`tmpls/CODE_TASK.md`](docs/.claude/tmpls/CODE_TASK.md) | Coding task template | +| [`commands/`](docs/.claude/commands/) | Slash command definitions (abcoder:task/abcoder:schedule/abcoder:recheck) | +| [`tmpls/ABCODER_CODE_TASK.md`](docs/.claude/tmpls/ABCODER_CODE_TASK.md) | Coding task template | ### Dependencies - Claude Code CLI -- abcoder MCP server (provides `mcp__abcoder` tools) -- sequential-thinking MCP server (provides `mcp__sequential_thinking` tools, optional) +- ABCoder MCP server (provides `mcp__abcoder` tools) +- Sequential-thinking MCP server (provides `mcp__sequential_thinking` tools, automatically configured by init-spec) > For detailed configuration, see [docs/.claude/README.md](docs/.claude/README.md) > Watch the demo video [here](https://github.com/cloudwego/abcoder/pull/141) +## Use ABCoder as a MCP server + +1. Install ABCoder: + + ```bash + go install github.com/cloudwego/abcoder@latest + ``` + +2. Use ABCoder to parse a repository to UniAST (JSON) + + ```bash + abcoder parse {language} {repo-path} -o xxx.json + ``` + + ABCoder will try to install any dependency automatically. + In case of failure (or if you want to customize installation), refer to the [docs](./docs/lsp-installation-en.md). + + For example, to parse a Go repository: + + ```bash + git clone https://github.com/cloudwego/localsession.git localsession + abcoder parse go localsession -o /abcoder-asts/localsession.json + ``` + + +3. Integrate ABCoder's MCP tools into your AI agent. + + ```json + { + "mcpServers": { + "abcoder": { + "command": "abcoder", + "args": [ + "mcp", + "{the-AST-directory}" + ] + } + } + } + ``` + + +4. Enjoy it! + + Try to click and watch the video below: + +
+ + [MCP](https://www.bilibili.com/video/BV14ggJzCEnK) + +
+ + +## Tips: + +- You can add more repo ASTs into the AST directory without restarting abcoder MCP server. + +- Try to use [the recommended prompt](llm/prompt/analyzer.md) and combine planning/memory tools like [sequential-thinking](https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking) in your AI agent. + ## Use ABCoder as an Agent (WIP) diff --git a/docs/.claude/hooks/abcoder/reminder.sh b/docs/.claude/hooks/abcoder/reminder.sh deleted file mode 100755 index f8596a6..0000000 --- a/docs/.claude/hooks/abcoder/reminder.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/zsh -cat <This is a reminder that when executing the ABCoder code analysis workflow, after locating the target node, you MUST use the get_ast_node tool. It is required to recursively call get_ast_node to obtain the complete AST node information, including type, code, position, and related relationships (dependency, reference, inheritance, implementation, grouping node IDs).", - "hookSpecificOutput": { - "hookEventName": "PostToolUse", - } -} -EOF -exit 0 diff --git a/docs/.claude/settings.json b/docs/.claude/settings.json deleted file mode 100644 index d051471..0000000 --- a/docs/.claude/settings.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "permissions": { - "allow": [ - "Edit(\\.md)", - "Read(~/.claude/**)", - "mcp__abcoder", - "mcp__sequential-thinking" - ] - }, - "hooks": { - "PreToolUse": [ - { - "matcher": "mcp__abcoder__get_repo_structure|mcp__abcoder__get_file_structure|mcp__abcoder__get_package_structure|mcp__abcoder__get_ast_node", - "hooks": [ - { - "type": "command", - "command": "~/.claude/hooks/abcoder/parse.sh" - } - ] - } - ], - "PostToolUse": [ - { - "matcher": "mcp__abcoder__list_repos", - "hooks": [ - { - "type": "command", - "command": "~/.claude/hooks/abcoder/prompt.sh" - } - ] - }, - { - "matcher": "mcp__abcoder__get_repo_structure|mcp__abcoder__get_package_structure|mcp__abcoder__get_file_structure", - "hooks": [ - { - "type": "command", - "command": "~/.claude/hooks/abcoder/reminder.sh" - } - ] - } - ] - } -} diff --git a/docs/.claude/README.md b/docs/claude-code-spec.md similarity index 93% rename from docs/.claude/README.md rename to docs/claude-code-spec.md index 293dc77..4f72806 100644 --- a/docs/.claude/README.md +++ b/docs/claude-code-spec.md @@ -14,7 +14,7 @@ Claude Code 的 AST 驱动开发配置,通过 MCP 工具、钩子和斜杠命 │ └── reminder.sh # 提醒递归调用 get_ast_node ├── commands/ # 斜杠命令定义 │ ├── abcoder:task.md # /abcoder:task - 创建编码任务 -│ ├── abcoder:schd.md # /abcoder:schd - 设计实现方案 +│ ├── abcoder:schedule.md # /abcoder:schedule - 设计实现方案 │ └── abcoder:recheck.md # /abcoder:recheck - 技术方案核对 └── tmpls/ # 文档模板 └── CODE_TASK.md # 编码任务模板 @@ -63,7 +63,7 @@ list_repos → get_repo_structure → get_package_structure → get_file_structu ## 斜杠命令 -### /abcoder:task <名称> +### /abcoder:task <任务名称> 创建 CODE_TASK 文档,生成 `./task/{{MMDD}}/{{NAME}}__CODE_TASK.md` @@ -73,7 +73,7 @@ list_repos → get_repo_structure → get_package_structure → get_file_structu - 涉及 curl: 提供完整命令和响应结构 - 提供具体验证方法 -### /abcoder:schd +### /abcoder:schedule <任务描述> 使用 mcp__abcoder 设计实现方案 @@ -82,7 +82,7 @@ list_repos → get_repo_structure → get_package_structure → get_file_structu - 优先最小改动 - 禁止编写代码、禁止使用 agent -### /abcoder:recheck <任务> +### /abcoder:recheck <任务名称> 批判性检查 CODE_TASK 技术可行性 @@ -97,7 +97,7 @@ list_repos → get_repo_structure → get_package_structure → get_file_structu 用户需求 │ ▼ -/abcoder:schd ──────────────→ 设计方案(abcoder分析) +/abcoder:schedule ──────────────→ 设计方案(abcoder分析) │ │ ▼ ▼ /abcoder:task ────────→ CODE_TASK(含技术规格) diff --git a/docs/.claude/CLAUDE.md b/internal/utils/assets/.claude/CLAUDE.md similarity index 100% rename from docs/.claude/CLAUDE.md rename to internal/utils/assets/.claude/CLAUDE.md diff --git a/docs/.claude/commands/README.md b/internal/utils/assets/.claude/commands/README.md similarity index 89% rename from docs/.claude/commands/README.md rename to internal/utils/assets/.claude/commands/README.md index 049276c..86fe9d9 100644 --- a/docs/.claude/commands/README.md +++ b/internal/utils/assets/.claude/commands/README.md @@ -10,7 +10,7 @@ Claude Code 斜杠命令定义,用于规范化和自动化开发工作流。 **执行流程**: 1. 创建 `./task/{{MMDD}}/` 目录 -2. 读取模板 `~/.claude/tmpls/CODE_TASK.md` +2. 读取模板 `{{CLAUDE_HOME_PATH}}/.claude/tmpls/ABCODER_CODE_TASK.md` 3. 根据任务上下文填充模板,生成 `./task/{{MMDD}}/{{NAME}}__CODE_TASK.md` 4. 列出外部依赖包(如有) 5. 提示创建成功 @@ -30,7 +30,7 @@ Claude Code 斜杠命令定义,用于规范化和自动化开发工作流。 --- -### /abcoder:schd - 设计实现方案 +### /abcoder:schedule - 设计实现方案 使用 mcp__abcoder 分析代码库,设计技术实现方案。 @@ -66,7 +66,7 @@ Claude Code 斜杠命令定义,用于规范化和自动化开发工作流。 ## 模板文件 -### CODE_TASK.md +### ABCODER_CODE_TASK.md 编码任务模板,定义任务清单格式。 @@ -89,7 +89,7 @@ Claude Code 斜杠命令定义,用于规范化和自动化开发工作流。 用户需求 │ ▼ -/abcoder:schd ──────────────→ 设计方案(abcoder分析) +/abcoder:schedule ──────────────→ 设计方案(abcoder分析) │ │ ▼ ▼ /abcoder:task ────────→ CODE_TASK(含技术规格) @@ -104,11 +104,11 @@ coding-executor ──────→ 执行实现 ## 文件位置 ``` -~/.claude/ +{{CLAUDE_HOME_PATH}}/.claude/ ├── commands/ │ ├── abcoder:task.md -│ ├── abcoder:schd.md +│ ├── abcoder:schedule.md │ └── abcoder:recheck.md └── tmpls/ - └── CODE_TASK.md + └── ABCODER_CODE_TASK.md ``` diff --git a/docs/.claude/commands/abcoder:recheck.md b/internal/utils/assets/.claude/commands/recheck.md similarity index 85% rename from docs/.claude/commands/abcoder:recheck.md rename to internal/utils/assets/.claude/commands/recheck.md index 9476d8c..326f190 100644 --- a/docs/.claude/commands/abcoder:recheck.md +++ b/internal/utils/assets/.claude/commands/recheck.md @@ -1,4 +1,5 @@ -# 系统指令: ReCheck +# 系统指令: 核对任务实现方案(二次对齐) + 理解任务 {1},并使用`mcp__abcoder`核对技术细节(下钻到`mcp__abcoder__get_ast_node`粒度); diff --git a/docs/.claude/commands/abcoder:schd.md b/internal/utils/assets/.claude/commands/schedule.md similarity index 81% rename from docs/.claude/commands/abcoder:schd.md rename to internal/utils/assets/.claude/commands/schedule.md index 52dbfff..0d6bef6 100644 --- a/docs/.claude/commands/abcoder:schd.md +++ b/internal/utils/assets/.claude/commands/schedule.md @@ -1,4 +1,4 @@ -# 系统指令:Schedule +# 系统指令:规划一个任务(输入:任务描述) 使用`mcp__abcoder`分析相关仓库(下钻到`mcp__abcoder__get_ast_node`查看细节),帮助用户设计实现方案。 @@ -7,6 +7,7 @@ - 优先采用直接、最小改动的实现方式,只有在用户明确要求时才增加复杂度。 - 严格限制修改影响面在所请求的结果范围内。 - 找出任何模糊或含糊不清的细节,并在修改文件前提出必要的后续问题。 -- 在Schdule阶段禁止编写代码,禁止使用agent。 +- 在Schedule阶段禁止编写代码,禁止使用agent。 + IMPORTANT: 必须从`mcp__abcoder__get_repo_strucure`开始 diff --git a/docs/.claude/commands/abcoder:task.md b/internal/utils/assets/.claude/commands/task.md similarity index 79% rename from docs/.claude/commands/abcoder:task.md rename to internal/utils/assets/.claude/commands/task.md index 69a0638..d212e89 100644 --- a/docs/.claude/commands/abcoder:task.md +++ b/internal/utils/assets/.claude/commands/task.md @@ -1,9 +1,9 @@ -# 系统指令:创建CODE_TASK +# 系统指令:基于当前上下文创建一个 SPEC 驱动的 CODE_TASK(输入:任务名称) 请帮我创建一个CODE_TASK。 1. `d=$(date +%m%d) && mkdir -p "./task/$d/" && echo "目录 ./task/$d/ 已创建"` 创建 `./task/{{MMDD}}/` 目录 -2. 读取模板文件 `~/.claude/tmpls/CODE_TASK.md` +2. 读取模板文件 `{{CLAUDE_HOME_PATH}}/.claude/tmpls/ABCODER_CODE_TASK.md` 3. 根据任务上下文,按照格式和要求填充模板,创建新文件 `./task/{{MMDD}}/{{NAME}}__CODE_TASK.md` 4. 告知用户这个`CODE_TASK`是否包含外部依赖;如果包含,请清晰列出完整的外部依赖包名称 5. 提示用户文件已创建成功,停止操作 diff --git a/docs/.claude/hooks/README.md b/internal/utils/assets/.claude/hooks/README.md similarity index 97% rename from docs/.claude/hooks/README.md rename to internal/utils/assets/.claude/hooks/README.md index 83615cc..d131e04 100644 --- a/docs/.claude/hooks/README.md +++ b/internal/utils/assets/.claude/hooks/README.md @@ -67,7 +67,7 @@ Claude Code 钩子系统,用于自动化 ABCoder 代码分析工作流。 ## 安装位置 ``` -~/.claude/hooks/abcoder/ +{{CLAUDE_HOME_PATH}}/.claude/hooks/abcoder/ ├── parse.sh ├── prompt.sh ├── reminder.sh diff --git a/docs/.claude/hooks/abcoder/abcoder-workflow.md b/internal/utils/assets/.claude/hooks/abcoder/abcoder-workflow.md similarity index 79% rename from docs/.claude/hooks/abcoder/abcoder-workflow.md rename to internal/utils/assets/.claude/hooks/abcoder/abcoder-workflow.md index a30b8c6..1d3bf62 100644 --- a/docs/.claude/hooks/abcoder/abcoder-workflow.md +++ b/internal/utils/assets/.claude/hooks/abcoder/abcoder-workflow.md @@ -20,8 +20,9 @@ 2. **代码定位** (repo→package→node→ast node relationship): - 2.1 **定位package**: 基于 `get_repo_structure` 返回的package list选择目标package - - 2.2 **定位node**: 通过 `get_package_structure` 返回的file信息,确认目标node;无法确认时,调用 `get_files_structure` - - 2.3 **确认ast node relationship**: 递归调用 `get_ast_node` 获取node详细(dependencies, references, inheritance, implementation, grouping) + - 2.2 **定位文件**: 通过 `get_package_structure` 返回的file信息,确认目标文件 + - 2.3 **定位节点**: 通过 `get_files_structure` 返回的node信息,确认目标节点 + - 2.4 **确认node详情**: 递归调用 `get_ast_node` 获取node详细(dependencies, references, inheritance, implementation, grouping) 3. **自我反思**: - 理解完整的code calling-chain、contextual-relationship diff --git a/internal/utils/assets/.claude/hooks/abcoder/need_update.sh b/internal/utils/assets/.claude/hooks/abcoder/need_update.sh new file mode 100755 index 0000000..2899692 --- /dev/null +++ b/internal/utils/assets/.claude/hooks/abcoder/need_update.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# 当 Claude Code 执行 Write 操作后,更新 _need_update 文件为 1 + +# 错误处理 +set -euo pipefail + +# 获取脚本所在的绝对路径 +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# 定义 _need_update 文件路径(放在脚本所在目录) +need_update_file="${script_dir}/_need_update" + +# 写入 1 到文件 +echo "1" > "$need_update_file" + +# 输出 JSON +jq -n --arg file "$need_update_file" '{ + "continue": true, + "hookSpecificOutput": { + "hookEventName": "PostToolUse", + "additionalContext": ("Write 操作完成,已更新 " + $file + " 为 1") + } +}' + +exit 0 diff --git a/docs/.claude/hooks/abcoder/parse.sh b/internal/utils/assets/.claude/hooks/abcoder/parse.sh similarity index 51% rename from docs/.claude/hooks/abcoder/parse.sh rename to internal/utils/assets/.claude/hooks/abcoder/parse.sh index 18c5247..3661e93 100755 --- a/docs/.claude/hooks/abcoder/parse.sh +++ b/internal/utils/assets/.claude/hooks/abcoder/parse.sh @@ -72,7 +72,7 @@ abc() { mkdir -p ~/.asts/ # 执行实际命令 - abcoder parse "${lang}" "${repo_path}" -o "/Users/bytedance/.asts/${repo_name}.json" + abcoder parse "${lang}" "${repo_path}" -o "~/.asts/${repo_name}.json" else # 如果不是预期的 parse 命令格式,直接将参数传递给原始 abcoder 命令 abcoder "$@" @@ -81,6 +81,12 @@ abc() { # LOG_FILE="/tmp/claude-hook-debug.log" +# 获取脚本所在的绝对路径 +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# 定义 _need_update 文件路径 +need_update_file="${script_dir}/_need_update" + input=$(cat) repo_name=$(echo "$input" | jq -r '.tool_input.repo_name // ""') cwd=$(echo "$input" | jq -r '.cwd // ""') @@ -89,71 +95,82 @@ cwd=$(echo "$input" | jq -r '.cwd // ""') # echo "repo_name: $repo_name" >> "$LOG_FILE" # echo "cwd: $cwd" >> "$LOG_FILE" +# 读取 _need_update 文件判断是否需要强制更新 +force_update=0 +if [[ -f "$need_update_file" ]]; then + need_update_value=$(cat "$need_update_file" 2>/dev/null || echo "0") + if [[ "$need_update_value" == "1" ]]; then + force_update=1 + # 强制更新后,将 _need_update 重置为 0 + echo "0" > "$need_update_file" + fi +fi + # 复用现有的 get_basename 函数 current_base_name=$(get_basename "$cwd") -# 检测项目信息(语言和仓库标识) +# 检测项目信息(语言和仓库标识符) project_info=$(detect_project_info "$cwd") project_lang=$(echo "$project_info" | cut -d'|' -f1) project_identifier=$(echo "$project_info" | cut -d'|' -f2) -# echo "Detected project language: $project_lang" >> "$LOG_FILE" -# echo "Detected project identifier: $project_identifier" >> "$LOG_FILE" - -if [ "$repo_name" = "$cwd" ] || [ "$current_base_name" = "$repo_name" ] || [ "$project_identifier" = "$repo_name" ]; then - # echo "Path or identifier matched, executing abc parse..." >> "$LOG_FILE" - - # 检查是否检测到目标语言 - if [[ "$project_lang" == "unknown" ]]; then - jq -n '{ - "decision": "block", - "reason": "未检测到支持的语言(仅支持 Go 和 TypeScript)", - "hookSpecificOutput": { - "hookEventName": "PreToolUse", - "additionalContext": "请确保项目是 Go 或 TypeScript 类型" - } - }' - exit 0 - fi - +# 优化判断逻辑:只要检测到有效项目,就执行 parse +if [[ "$project_lang" != "unknown" ]]; then # 捕获标准输出和错误输出 output_file=$(mktemp) error_file=$(mktemp) - # 修改:使用检测到的语言执行 parse 命令(替换原有的固定 ts) - if abcoder parse "$project_lang" . >"$output_file" 2>"$error_file"; then - # echo "abc parse succeeded" >> "$LOG_FILE" - # cat "$output_file" >> "$LOG_FILE" + # 使用检测到的语言执行 parse 命令 + # 确保输出目录存在 + mkdir -p ~/.asts/ + ast_output_file=~/.asts/$(echo "$project_identifier" | sed 's|/|_|g').json + + # 检查 AST 文件是否存在且更新时间小于 3 分钟(缓存优化) + # 如果 force_update=1,则跳过缓存检查,强制重新 parse + if [[ $force_update -eq 0 && -f "$ast_output_file" ]]; then + file_age_seconds=$(($(date +%s) - $(stat -f %m "$ast_output_file" 2>/dev/null || stat -c %Y "$ast_output_file" 2>/dev/null))) + if [[ $file_age_seconds -lt 180 ]]; then + jq -n --arg lang "$project_lang" --arg repo "$project_identifier" --arg age "$file_age_seconds" '{ + "continue": true, + "systemMessage": ("abcoder AST 缓存命中(语言:" + $lang + ",仓库:" + $repo + ")。文件更新于 " + $age + " 秒前(小于3分钟更新阈值),跳过 parse 操作。") + }' + exit 0 + fi + fi - jq -n --arg lang "$project_lang" '{ - "systemMessage": "abcoder parse 已成功完成(语言:\($lang))。AST文件已生成,可以继续分析代码。" - }' + # 使用检测到的语言执行 parse 命令,并输出到 AST 目录 + if abcoder parse "$project_lang" . -o "$ast_output_file" >"$output_file" 2>"$error_file"; then + msg_prefix="[定时更新]" + if [[ $force_update -eq 1 ]]; then + msg_prefix="[检测到变更] " + fi + jq -n --arg lang "$project_lang" --arg repo "$project_identifier" --arg prefix "$msg_prefix" '{ + "continue": true, + "systemMessage": ($prefix + "abcoder parse 已成功完成(语言:" + $lang + ",仓库:" + $repo + ")。AST文件已生成,可以继续分析代码。") + }' else exit_code=$? - # echo "abc parse FAILED with exit code $exit_code" >> "$LOG_FILE" - # echo "STDOUT:" >> "$LOG_FILE" - # cat "$output_file" >> "$LOG_FILE" - # echo "STDERR:" >> "$LOG_FILE" - # cat "$error_file" >> "$LOG_FILE" - # 读取错误信息 error_msg=$(cat "$error_file" | tail -20) - jq -n --arg code "$exit_code" --arg err "$error_msg" --arg lang "$project_lang" '{ + jq -n --arg code "$exit_code" --arg err "$error_msg" --arg lang "$project_lang" --arg repo "$project_identifier" '{ "decision": "block", - "reason": ("abcoder parse 失败(语言:\($lang),退出码: " + $code + ")。错误信息:\n" + $err + "\n\n可能的原因:\n1. 项目配置文件有问题(Go: go.mod;TS: tsconfig.json)\n2. 缺少依赖包\n3. 代码语法错误\n\n建议:\n- Go 项目:运行 'go mod tidy' 和 'go build' 检查\n- TS 项目:运行 'npm install' 和 'tsc --noEmit' 检查"), + "reason": ("abcoder parse 失败(语言:" + $lang + ",仓库:" + $repo + ",退出码: " + $code + ")。错误信息:\n" + $err + "\n\n可能的原因:\n1. 项目配置文件有问题(Go: go.mod;TS: tsconfig.json)\n2. 缺少依赖包\n3. 代码语法错误\n\n建议:\n- Go 项目:运行 'go mod tidy' 和 'go build' 检查\n- TS 项目:运行 'npm install' 和 'tsc --noEmit' 检查"), "hookSpecificOutput": { "hookEventName": "PreToolUse", - "additionalContext": ("解析失败,需要修复后重试") + "additionalContext": "解析失败,需要修复后重试" } }' fi # 清理临时文件 - trash "$output_file" "$error_file" + trash "$output_file" "$error_file" 2>/dev/null || rm -f "$output_file" "$error_file" else - # echo "Path did not match" >> "$LOG_FILE" - echo '{}' + # 当前目录不是支持的项目,返回空对象 + jq -n '{ + "decision": "block", + "reason": "当前目录未检测到支持的语言(仅支持 Go 和 TypeScript),请确保项目是 Go 或 TypeScript 类型" + }' fi exit 0 diff --git a/docs/.claude/hooks/abcoder/prompt.sh b/internal/utils/assets/.claude/hooks/abcoder/prompt.sh similarity index 58% rename from docs/.claude/hooks/abcoder/prompt.sh rename to internal/utils/assets/.claude/hooks/abcoder/prompt.sh index 454acb3..eb52d1c 100755 --- a/docs/.claude/hooks/abcoder/prompt.sh +++ b/internal/utils/assets/.claude/hooks/abcoder/prompt.sh @@ -7,12 +7,12 @@ set -euo pipefail # 验证文件存在 -SOP_FILE="$HOME/.claude/hooks/abcoder/abcoder-workflow.md" +SOP_FILE="/Users/bytedance/github/github.com/cloudwego/abcoder2/.claude/hooks/abcoder/abcoder-workflow.md" # echo "DEBUG: Checking file: $SOP_FILE" >&2 if [[ ! -f "$SOP_FILE" ]]; then # echo "DEBUG: File not found" >&2 - echo '{"decision": "block", "reason": "SOP file not found", "hookSpecificOutput": {"hookEventName": "PostToolUse"}}' + echo '{"ok": false, "reason": "SOP file not found", "hookSpecificOutput": {"hookEventName": "PostToolUse"}}' exit 0 fi @@ -22,7 +22,7 @@ fi SOP_CONTENT=$(cat "$SOP_FILE" | jq -Rs . 2>/dev/null) if [[ $? -ne 0 ]]; then # echo "DEBUG: jq failed" >&2 - echo '{"decision": "block", "reason": "Failed to process SOP content", "hookSpecificOutput": {"hookEventName": "PostToolUse"}}' + echo '{"ok": false, "reason": "Failed to process SOP content", "hookSpecificOutput": {"hookEventName": "PostToolUse"}}' exit 0 fi @@ -31,10 +31,10 @@ fi # 输出 JSON cat <This is a reminder that when executing the ABCoder code analysis workflow, after locating the target node, you MUST use the get_ast_node tool. It is required to recursively call get_ast_node to obtain the complete AST node information, including type, code, position, and related relationships (dependency, reference, inheritance, implementation, grouping node IDs).", + "hookSpecificOutput": { + "hookEventName": "PostToolUse" + } +} +EOF +exit 0 diff --git a/internal/utils/assets/.claude/settings.json b/internal/utils/assets/.claude/settings.json new file mode 100644 index 0000000..d1175b1 --- /dev/null +++ b/internal/utils/assets/.claude/settings.json @@ -0,0 +1,52 @@ +{ + "permissions": { + "allow": [ + "Edit(\\.md)", + "Read({{CLAUDE_HOME_PATH}}/.claude/**)", + "mcp__abcoder", + "mcp__sequential-thinking" + ] + }, + "hooks": { + "PreToolUse": [ + { + "matcher": "mcp__abcoder__list_repos|mcp__abcoder__get_repo_structure|mcp__abcoder__get_file_structure|mcp__abcoder__get_package_structure|mcp__abcoder__get_ast_node", + "hooks": [ + { + "type": "command", + "command": "{{CLAUDE_HOME_PATH}}/.claude/hooks/abcoder/parse.sh" + } + ] + } + ], + "PostToolUse": [ + { + "matcher": "Write|Edit", + "hooks": [ + { + "type": "command", + "command": "{{CLAUDE_HOME_PATH}}/.claude/hooks/abcoder/need_update.sh" + } + ] + }, + { + "matcher": "mcp__abcoder__list_repos", + "hooks": [ + { + "type": "command", + "command": "{{CLAUDE_HOME_PATH}}/.claude/hooks/abcoder/prompt.sh" + } + ] + }, + { + "matcher": "mcp__abcoder__get_repo_structure|mcp__abcoder__get_package_structure|mcp__abcoder__get_file_structure", + "hooks": [ + { + "type": "command", + "command": "{{CLAUDE_HOME_PATH}}/.claude/hooks/abcoder/reminder.sh" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/docs/.claude/tmpls/CODE_TASK.md b/internal/utils/assets/.claude/tmpls/ABCODER_CODE_TASK.md similarity index 100% rename from docs/.claude/tmpls/CODE_TASK.md rename to internal/utils/assets/.claude/tmpls/ABCODER_CODE_TASK.md diff --git a/internal/utils/cmd_init_spec.go b/internal/utils/cmd_init_spec.go new file mode 100644 index 0000000..a5fdf26 --- /dev/null +++ b/internal/utils/cmd_init_spec.go @@ -0,0 +1,274 @@ +/** + * Copyright 2026 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package utils + +import ( + "embed" + "encoding/json" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + + "github.com/cloudwego/abcoder/llm/log" +) + +//go:embed assets/.claude +var claudeFS embed.FS + +// claudeConfig represents the Claude Code configuration structure +type claudeConfig struct { + MCPServers map[string]mcpServerConfig `json:"mcpServers"` +} + +type mcpServerConfig struct { + Command string `json:"command"` + Args []string `json:"args"` +} + +// runInitSpec implements the init-spec command +func RunInitSpec(targetDir string) error { + if targetDir == "" { + // Default to current directory if not specified + cwd, err := os.Getwd() + if err != nil { + return fmt.Errorf("failed to get current directory: %w", err) + } + targetDir = cwd + } + + // Ensure targetDir is absolute + targetDirAbs, err := filepath.Abs(targetDir) + if err != nil { + return fmt.Errorf("failed to get absolute path: %w", err) + } + + // 1. Copy assets/.claude to targetDir/.claude + claudeDestDir := filepath.Join(targetDirAbs, ".claude") + if err := copyEmbeddedDir("assets/.claude", claudeDestDir, targetDirAbs); err != nil { + return fmt.Errorf("failed to copy .claude directory: %w", err) + } + log.Info("Copied .claude directory to %s", claudeDestDir) + + // 2. Get home directory for MCP server configuration + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("failed to get home directory: %w", err) + } + + // 3. Configure MCP servers in ~/.claude.json + // Get asts directory path from parse.sh hook (default ~/.asts) + astsDir := filepath.Join(homeDir, ".asts") + + // Create asts directory if it doesn't exist + if err := os.MkdirAll(astsDir, 0755); err != nil { + return fmt.Errorf("failed to create asts directory: %w", err) + } + + claudeConfigPath := filepath.Join(homeDir, ".claude.json") + if err := configureMCPServers(claudeConfigPath, astsDir); err != nil { + return fmt.Errorf("failed to configure MCP servers: %w", err) + } + log.Info("Configured MCP servers in %s", claudeConfigPath) + + // 4. Print success message + printSuccessMessage(targetDirAbs, claudeConfigPath, astsDir) + + return nil +} + +// copyEmbeddedDir copies an embedded directory to a destination directory +func copyEmbeddedDir(srcPath string, destDir string, projectRootDir string) error { + // First, ensure the destination directory exists + if err := os.MkdirAll(destDir, 0755); err != nil { + return fmt.Errorf("failed to create destination directory %s: %w", destDir, err) + } + + // Track md files to process after copying + var mdFilesToReplace []string + + err := fs.WalkDir(claudeFS, srcPath, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + // Calculate relative path from srcPath + relPath, err := filepath.Rel(srcPath, path) + if err != nil { + return err + } + + // Skip the root directory itself + if relPath == "." { + return nil + } + + destPath := filepath.Join(destDir, relPath) + + if d.IsDir() { + // Create directory + return os.MkdirAll(destPath, 0755) + } + + // Ensure parent directory exists before writing file + parentDir := filepath.Dir(destPath) + if err := os.MkdirAll(parentDir, 0755); err != nil { + return fmt.Errorf("failed to create parent directory %s: %w", parentDir, err) + } + + // Rename command files with abcoder: prefix + if strings.HasPrefix(relPath, "commands/") { + baseName := filepath.Base(relPath) + switch baseName { + case "recheck.md": + destPath = filepath.Join(filepath.Dir(destPath), "abcoder:recheck.md") + case "schedule.md": + destPath = filepath.Join(filepath.Dir(destPath), "abcoder:schedule.md") + case "task.md": + destPath = filepath.Join(filepath.Dir(destPath), "abcoder:task.md") + } + } + + // Copy file + data, err := claudeFS.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read embedded file %s: %w", path, err) + } + + if err := os.WriteFile(destPath, data, 0644); err != nil { + return fmt.Errorf("failed to write file %s: %w", destPath, err) + } + + // Set executable permission for shell scripts + if strings.HasSuffix(relPath, ".sh") { + if err := os.Chmod(destPath, 0755); err != nil { + return fmt.Errorf("failed to set executable permission for %s: %w", destPath, err) + } + } + + // Track md and json files for placeholder replacement + if strings.HasSuffix(relPath, ".md") || strings.HasSuffix(relPath, ".json") || strings.HasSuffix(relPath, "prompt.sh") { + mdFilesToReplace = append(mdFilesToReplace, destPath) + } + + return nil + }) + + if err != nil { + return err + } + + // Replace {{CLAUDE_HOME_PATH}} placeholder in md files with project root directory + for _, mdFile := range mdFilesToReplace { + if err := replaceClaudeHomePlaceholder(mdFile, projectRootDir); err != nil { + log.Info("Failed to replace placeholder in %s: %v", mdFile, err) + } + } + + return nil +} + +// replaceClaudeHomePlaceholder replaces {{CLAUDE_HOME_PATH}} with actual project root directory path +func replaceClaudeHomePlaceholder(filePath string, projectRootDir string) error { + data, err := os.ReadFile(filePath) + if err != nil { + return fmt.Errorf("failed to read file %s: %w", filePath, err) + } + + content := string(data) + newContent := strings.ReplaceAll(content, "{{CLAUDE_HOME_PATH}}", projectRootDir) + + if err := os.WriteFile(filePath, []byte(newContent), 0644); err != nil { + return fmt.Errorf("failed to write file %s: %w", filePath, err) + } + + return nil +} + +// configureMCPServers configures MCP servers in the Claude config file +func configureMCPServers(configPath string, astsDir string) error { + var config claudeConfig + + // Read existing config if it exists + if data, err := os.ReadFile(configPath); err == nil { + if err := json.Unmarshal(data, &config); err != nil { + return fmt.Errorf("failed to parse existing config: %w", err) + } + } else if !os.IsNotExist(err) { + return fmt.Errorf("failed to read config file: %w", err) + } + + // Initialize mcpServers map if nil + if config.MCPServers == nil { + config.MCPServers = make(map[string]mcpServerConfig) + } + + // Add/Update abcoder MCP server + config.MCPServers["abcoder"] = mcpServerConfig{ + Command: "abcoder", + Args: []string{"mcp", astsDir}, + } + + // Add sequential-thinking MCP server + config.MCPServers["sequential-thinking"] = mcpServerConfig{ + Command: "npx", + Args: []string{"-y", "@modelcontextprotocol/server-sequential-thinking"}, + } + + // Write the config file + data, err := json.MarshalIndent(config, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal config: %w", err) + } + + if err := os.WriteFile(configPath, data, 0644); err != nil { + return fmt.Errorf("failed to write config file: %w", err) + } + + return nil +} + +// printSuccessMessage prints a success message with instructions +func printSuccessMessage(targetDir string, configPath string, astsDir string) { + fmt.Printf(` +✓ ABCoder Claude Code integration setup completed! + +Configuration files: + .claude directory: %s + Claude Code config: %s + AST storage directory: %s + +MCP servers configured: + - abcoder: for code analysis using AST + - sequential-thinking: for complex problem decomposition + +Next steps: + 1. Ensure abcoder is installed and in your PATH: + go install github.com/cloudwego/abcoder@latest + + 2. Restart Claude Code to apply the configuration + + 3. Use ABCoder tools in Claude Code: + - /abcoder:schedule - Analyze codebase and design solution + - /abcoder:task - Create coding task + - /abcoder:recheck - Verify solution + +For more information, see: + - https://github.com/cloudwego/abcoder +`, targetDir, configPath, astsDir) +} diff --git a/main.go b/main.go index ffab2ea..9148f15 100644 --- a/main.go +++ b/main.go @@ -39,6 +39,7 @@ import ( "path/filepath" "strings" + interutils "github.com/cloudwego/abcoder/internal/utils" "github.com/cloudwego/abcoder/lang" "github.com/cloudwego/abcoder/lang/log" "github.com/cloudwego/abcoder/lang/uniast" @@ -56,6 +57,7 @@ Action: write write the specific UniAST back to codes mcp run as a MCP server for all repo ASTs (*.json) in the specific directory agent run as an Agent for all repo ASTs (*.json) in the specific directory. WIP: only support code-analyzing at present. + init-spec initialize ABCoder integration for Claude Code (copies .claude directory and configures MCP servers) version print the version of abcoder Language: go for golang codes @@ -195,6 +197,29 @@ func main() { os.Exit(1) } + case "init-spec": + // Parse flags only, uri is optional and defaults to current directory + flags.Parse(os.Args[2:]) + + var uri string + if flagHelp != nil && *flagHelp { + flags.Usage() + os.Exit(0) + } + + if flagVerbose != nil && *flagVerbose { + log.SetLogLevel(log.DebugLevel) + } + + if len(os.Args) > 2 && !strings.HasPrefix(os.Args[2], "-") { + uri = os.Args[2] + } + + if err := interutils.RunInitSpec(uri); err != nil { + log.Error("Failed to init-spec: %v\n", err) + os.Exit(1) + } + case "agent": _, uri := parseArgsAndFlags(flags, false, flagHelp, flagVerbose) if uri == "" {