Skip to content

Commit 3f3768f

Browse files
authored
Merge branch 'main' into feat/more_accurate_prompt
2 parents 46c2429 + a0e6cce commit 3f3768f

11 files changed

Lines changed: 127 additions & 38 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ see [UniAST Specification](docs/uniast-zh.md)
8181
## Tips:
8282
8383
- You can add more repo ASTs into the AST directory without restarting abcoder MCP server.
84-
85-
- Try to use [the recommened 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.
84+
85+
- 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.
8686
8787
8888
## Claude Code Integration

docs/parser-en.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type LanguageSpec interface {
3838
WorkSpace(root string) (map[string]string, error)
3939

4040
// give an absolute file path and returns its module name and package path
41-
// external path should alse be supported
41+
// external path should also be supported
4242
// FIXEM: some language (like rust) may have sub-mods inside a file, but we still consider it as a unity mod here
4343
NameSpace(path string) (string, string, error)
4444

docs/parser-zh.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
- CheckRepo():检查用户仓库情况,根据各语言规范额处理工具链等问题,并返回默认打开的第一个文件(用于触发 LSP server),以及等候 sever 初始化完成的时间(根据仓库大小来决定)
3030
- **LanguageSpec interface**: 核心模块,用于处理非 LSP 通用的语法信息、比如判断一个 token 是否是标准库的符号、函数签名解析等:
3131

32-
### LaunguageSpec
32+
### LanguageSpec
3333

3434
用于在 LSP 符号收集过程中转换为 UniAST 所需信息,并且这些信息非 LSP 通用定义
3535

@@ -40,7 +40,7 @@ type LanguageSpec interface {
4040
WorkSpace(root string) (map[string]string, error)
4141

4242
// give an absolute file path and returns its module name and package path
43-
// external path should alse be supported
43+
// external path should also be supported
4444
// FIXEM: some language (like rust) may have sub-mods inside a file, but we still consider it as a unity mod here
4545
NameSpace(path string) (string, string, error)
4646

docs/uniast-en.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ An independent code compilation unit, corresponding to ModPath in Identity, cont
154154
- Dependencies: Dictionary of third-party dependency modules for module building {ModName}: {ModPath}
155155

156156

157-
- Packages: Contains subpackages, {PkgPath}: {Pacakge AST} dictionary
157+
- Packages: Contains subpackages, {PkgPath}: {Package AST} dictionary
158158

159159

160160
- Files: Module file information, where the key is the **path relative to the repo**. It is recommended to include all repository files here to facilitate writer rewriting
@@ -638,7 +638,7 @@ const (
638638
FUNC
639639
// Struct、TypeAlias、Enum...
640640
TYPE
641-
// Global Varable or Global Const
641+
// Global Variable or Global Const
642642
VAR
643643
)
644644
```

docs/uniast-zh.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ Universal Abstract-Syntax-Tree 是 ABCoder 建立的一种 LLM 亲和、语言
154154
- Dependencies: 模块构建的第三方依赖模块字典 {ModName}: {ModPath}
155155

156156

157-
- Packages: 包含的子包,{PkgPath}: {Pacakge AST} 字典
157+
- Packages: 包含的子包,{PkgPath}: {Package AST} 字典
158158

159159

160160
- Files: 模块文件信息,key 为**相对 repo 的路径。**这里建议包括仓库所有文件,方便 writer 回写
@@ -638,7 +638,7 @@ const (
638638
FUNC
639639
// Struct、TypeAlias、Enum...
640640
TYPE
641-
// Global Varable or Global Const
641+
// Global Variable or Global Const
642642
VAR
643643
)
644644
```

lang/golang/parser/parser.go

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type GoParser struct {
4848
files map[string][]byte
4949
exclues []*regexp.Regexp
5050
cgoPkgs map[string]bool // CGO packages
51+
workDirs map[string]bool // directories that are in go.work scope
5152
}
5253

5354
type moduleInfo struct {
@@ -97,10 +98,33 @@ func newGoParser(name string, homePageDir string, opts Options) *GoParser {
9798
}
9899

99100
func (p *GoParser) collectGoMods(startDir string) error {
100-
hasGoWork := false
101+
var workFiles []string
102+
err := filepath.Walk(startDir, func(path string, info fs.FileInfo, err error) error {
103+
if err != nil || !strings.HasSuffix(path, "go.work") {
104+
return nil
105+
}
106+
workFiles = append(workFiles, path)
107+
return nil
108+
})
109+
if err != nil {
110+
return err
111+
}
112+
113+
p.workDirs = make(map[string]bool)
114+
for _, workPath := range workFiles {
115+
wf, err := parseGoWork(workPath)
116+
if err != nil {
117+
fmt.Fprintf(os.Stderr, "failed to parse go.work file %s: %v\n", workPath, err)
118+
continue
119+
}
120+
for _, useDir := range wf.UseDirs {
121+
p.workDirs[useDir] = true
122+
}
123+
}
124+
fmt.Printf("go work effective dirs: %v\n", p.workDirs)
101125
deps := map[string]string{}
102126
var cgoPkgs map[string]bool
103-
err := filepath.Walk(startDir, func(path string, info fs.FileInfo, err error) error {
127+
err = filepath.Walk(startDir, func(path string, info fs.FileInfo, err error) error {
104128
if err != nil || !strings.HasSuffix(path, "go.mod") {
105129
return nil
106130
}
@@ -117,7 +141,7 @@ func (p *GoParser) collectGoMods(startDir string) error {
117141
p.repo.Modules[name] = newModule(name, rel)
118142
p.modules = append(p.modules, newModuleInfo(name, rel, name))
119143

120-
deps, hasGoWork, cgoPkgs, err = getDeps(filepath.Dir(path), hasGoWork)
144+
deps, cgoPkgs, err = getDeps(filepath.Dir(path), p.workDirs)
121145
if err != nil {
122146
return err
123147
}
@@ -161,31 +185,40 @@ type dep struct {
161185
CgoFiles []string `json:"CgoFiles"`
162186
}
163187

164-
func getDeps(dir string, goWork bool) (a map[string]string, hasGoWork bool, cgoPkgs map[string]bool, err error) {
188+
func getDeps(dir string, workDirs map[string]bool) (a map[string]string, cgoPkgs map[string]bool, err error) {
165189
cgoPkgs = make(map[string]bool)
166-
// run go mod tidy first to ensure all dependencies are resolved
190+
absDir, err := filepath.Abs(dir)
191+
if err != nil {
192+
return nil, cgoPkgs, fmt.Errorf("failed to get absolute path: %w", err)
193+
}
194+
195+
inWorkSpace := false
196+
for workDir := range workDirs {
197+
if absDir == workDir || strings.HasPrefix(absDir, workDir+string(filepath.Separator)) {
198+
inWorkSpace = true
199+
break
200+
}
201+
}
202+
167203
cmd := exec.Command("go", "mod", "tidy", "-e")
168204
cmd.Dir = dir
169-
cmd.Env = append(os.Environ(), "GONOSUMDB=*")
205+
cmd.Env = append(os.Environ(), "GONOSUMDB=*", "GOTOOLCHAIN=local")
170206
output, err := cmd.CombinedOutput()
171207
if err != nil {
172208
fmt.Fprintf(os.Stderr, "failed to execute 'go mod tidy', err: %v, output: %s, remove go.sum file reexecute\n", err, string(output))
173209
os.Remove(filepath.Join(dir, "go.sum"))
174210
cmd = exec.Command("go", "mod", "tidy", "-e")
175211
cmd.Dir = dir
176-
cmd.Env = append(os.Environ(), "GOSUMDB=off")
212+
cmd.Env = append(os.Environ(), "GOSUMDB=off", "GOTOOLCHAIN=local")
177213
output, err = cmd.CombinedOutput()
178214
if err != nil {
179-
return nil, hasGoWork, cgoPkgs, fmt.Errorf("failed to execute 'go mod tidy', err: %v, output: %s", err, string(output))
215+
return nil, cgoPkgs, fmt.Errorf("failed to execute 'go mod tidy', err: %v, output: %s", err, string(output))
180216
}
181217
}
182218
if hasNoDeps(filepath.Join(dir, "go.mod")) {
183-
return map[string]string{}, hasGoWork, cgoPkgs, nil
219+
return map[string]string{}, cgoPkgs, nil
184220
}
185-
// -mod=mod to use go mod when go mod is inconsistent with go vendor
186-
// if go.work exist, it's no need to set -mod=mod
187-
if _, err = os.Stat(filepath.Join(dir, "go.work")); err == nil || goWork {
188-
hasGoWork = true
221+
if inWorkSpace {
189222
cmd = exec.Command("go", "list", "-e", "-json", "all")
190223
} else {
191224
cmd = exec.Command("go", "list", "-e", "-json", "-mod=mod", "all")
@@ -194,12 +227,12 @@ func getDeps(dir string, goWork bool) (a map[string]string, hasGoWork bool, cgoP
194227
cmd.Env = append(os.Environ(), "GOSUMDB=off")
195228
output, err = cmd.CombinedOutput()
196229
if err != nil {
197-
return nil, hasGoWork, cgoPkgs, fmt.Errorf("failed to execute 'go list -json all', err: %v, output: %s, cmd string: %s, dir: %s", err, string(output), cmd.String(), dir)
230+
return nil, cgoPkgs, fmt.Errorf("failed to execute 'go list -json all', err: %v, output: %s, cmd string: %s, dir: %s", err, string(output), cmd.String(), dir)
198231
}
199232
// ignore content until first open
200233
index := strings.Index(string(output), "{")
201234
if index == -1 {
202-
return nil, hasGoWork, cgoPkgs, fmt.Errorf("failed to find '{' in output, output: %s", string(output))
235+
return nil, cgoPkgs, fmt.Errorf("failed to find '{' in output, output: %s", string(output))
203236
}
204237
if index > 0 {
205238
log.Info("go list skip prefix, output: %s", string(output[:index]))
@@ -213,7 +246,7 @@ func getDeps(dir string, goWork bool) (a map[string]string, hasGoWork bool, cgoP
213246
if err.Error() == "EOF" {
214247
break
215248
}
216-
return nil, hasGoWork, cgoPkgs, fmt.Errorf("failed to decode json: %v, output: %s", err, string(output))
249+
return nil, cgoPkgs, fmt.Errorf("failed to decode json: %v, output: %s", err, string(output))
217250
}
218251
module := mod.Module
219252
// golang internal package, ignore it.
@@ -239,7 +272,7 @@ func getDeps(dir string, goWork bool) (a map[string]string, hasGoWork bool, cgoP
239272
}
240273
}
241274
}
242-
return deps, hasGoWork, cgoPkgs, nil
275+
return deps, cgoPkgs, nil
243276
}
244277

245278
// ParseRepo parse the entiry repo from homePageDir recursively until end
@@ -263,6 +296,7 @@ func (p *GoParser) ParseModule(mod *Module, dir string) (err error) {
263296
// run go mod tidy before parse
264297
cmd := exec.Command("go", "mod", "tidy")
265298
cmd.Dir = dir
299+
cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
266300
buf := bytes.NewBuffer(nil)
267301
cmd.Stderr = buf
268302
cmd.Stdout = buf

lang/golang/parser/utils.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"os"
2424
"os/exec"
2525
"path"
26+
"path/filepath"
2627
"regexp"
2728
"strings"
2829
"sync"
@@ -319,3 +320,57 @@ func getCommitHash(dir string) (string, error) {
319320
}
320321
return strings.TrimSpace(string(output)), nil
321322
}
323+
324+
type workFile struct {
325+
Dir string
326+
UseDirs []string
327+
Replaces []workReplace
328+
}
329+
330+
type workReplace struct {
331+
OldPath string
332+
NewPath string
333+
}
334+
335+
func parseGoWork(workFilePath string) (*workFile, error) {
336+
content, err := os.ReadFile(workFilePath)
337+
if err != nil {
338+
return nil, fmt.Errorf("failed to read go.work file: %w", err)
339+
}
340+
341+
wf, err := modfile.ParseWork(workFilePath, content, nil)
342+
if err != nil {
343+
return nil, fmt.Errorf("failed to parse go.work file: %w", err)
344+
}
345+
346+
workDir := filepath.Dir(workFilePath)
347+
result := &workFile{
348+
Dir: workDir,
349+
UseDirs: make([]string, 0, len(wf.Use)),
350+
Replaces: make([]workReplace, 0, len(wf.Replace)),
351+
}
352+
353+
for _, use := range wf.Use {
354+
if use.Path == "" {
355+
continue
356+
}
357+
usePath := use.Path
358+
if !filepath.IsAbs(usePath) {
359+
usePath = filepath.Join(workDir, usePath)
360+
}
361+
absPath, err := filepath.Abs(usePath)
362+
if err != nil {
363+
continue
364+
}
365+
result.UseDirs = append(result.UseDirs, absPath)
366+
}
367+
368+
for _, rep := range wf.Replace {
369+
result.Replaces = append(result.Replaces, workReplace{
370+
OldPath: rep.Old.Path,
371+
NewPath: rep.New.Path,
372+
})
373+
}
374+
375+
return result, nil
376+
}

llm/prompt/analyzer.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
You are a code-analysis expert. Based on the Abstract-Syntax-Tree (AST) of a specific code repository and using relevant tools, you will answer user questions.
33

44
# Available Tools
5-
- `list_repos`: check the avaliable repos and their correct name
5+
- `list_repos`: check the available repos and their correct name
66
- `get_repo_structure`: Retrieve the structural information of a specified code repository, including lists of modules and packages.
77
- `get_package_structure`: Obtain the structural information of a specified package, including lists of files and node names.
88
- `get_ast_node`: Fetch the complete AST node information of a specified node, including its type, code, location, and related dependency (dependencies), reference (references), inheritance (inherits), implementation (implements), and grouping (groups) node IDs.

llm/tool/mcp.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828

2929
type MCPConfig struct {
3030
Type MCPType
31-
Comand string
31+
Command string
3232
Args []string
3333
Envs []string
3434
SSEURL string
@@ -50,10 +50,10 @@ func NewMCPClient(opts MCPConfig) (*MCPClient, error) {
5050
var err error
5151
switch opts.Type {
5252
case MCPTypeStdio:
53-
if opts.Comand == "" {
54-
return nil, errors.New("comand is empty")
53+
if opts.Command == "" {
54+
return nil, errors.New("command is empty")
5555
}
56-
cli, err = client.NewStdioMCPClient(opts.Comand, opts.Envs, opts.Args...)
56+
cli, err = client.NewStdioMCPClient(opts.Command, opts.Envs, opts.Args...)
5757
if err != nil {
5858
return nil, err
5959
}
@@ -112,7 +112,7 @@ func GetSequentialThinkingTools(ctx context.Context) ([]Tool, error) {
112112
sync.OnceFunc(func() {
113113
cli, err := NewMCPClient(MCPConfig{
114114
Type: MCPTypeStdio,
115-
Comand: "npx",
115+
Command: "npx",
116116
Args: []string{
117117
"-y",
118118
"@modelcontextprotocol/server-sequential-thinking",
@@ -136,7 +136,7 @@ func GetGitTools(ctx context.Context) ([]Tool, error) {
136136
sync.OnceFunc(func() {
137137
cli, err := NewMCPClient(MCPConfig{
138138
Type: MCPTypeStdio,
139-
Comand: "uvx",
139+
Command: "uvx",
140140
Args: []string{
141141
"mcp-server-git",
142142
},

llm/tool/mcp_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626
func TestMCPClient(t *testing.T) {
2727
cli, err := NewMCPClient(MCPConfig{
2828
Type: MCPTypeStdio,
29-
Comand: "npx",
29+
Command: "npx",
3030
Args: []string{
3131
"-y",
3232
"@modelcontextprotocol/server-sequential-thinking",

0 commit comments

Comments
 (0)