Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,24 @@ linters:
- common-false-positives
- legacy
- std-error-handling
rules:
# QF1012 (Fprintf over WriteString(Sprintf(...))) is an opinionated style
# quickfix; this codebase uses strings.Builder.WriteString consistently.
- linters:
- staticcheck
text: QF1012
# Test files routinely repeat small literals; constants add noise there.
- path: _test\.go
linters:
- goconst
- dupl
# The per-language doc integrations (godoc/rustdoc/pydoc) are
# intentionally parallel implementations of the same pattern, so the
# structural duplication and repeated command literals are by design.
- path: internal/(subcmd|mcptool|app)/(godoc|rustdoc|pydoc)\.go
linters:
- dupl
- goconst
paths:
- third_party$
- builtin$
Expand Down
9 changes: 6 additions & 3 deletions internal/app/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import (
"github.com/fpt/go-dev-mcp/internal/repository"
)

// declTypeFunction is the Declaration.Type value for functions and methods.
const declTypeFunction = "function"

type Declaration struct {
Name string
Type string // "function", "type", "interface", "struct", "const", "var"
Expand Down Expand Up @@ -101,7 +104,7 @@ func ExtractFunctionNames(
for _, result := range results {
var functionDeclarations []Declaration
for _, decl := range result.Declarations {
if decl.Type == "function" {
if decl.Type == declTypeFunction {
functionDeclarations = append(functionDeclarations, decl)
}
}
Expand Down Expand Up @@ -178,11 +181,11 @@ func extractDeclarationsFromFile(filePath string) []Declaration {
name = receiverType + "." + d.Name.Name
info = "method on " + receiverType
} else {
info = "function"
info = declTypeFunction
}
decl := Declaration{
Name: name,
Type: "function",
Type: declTypeFunction,
Info: info,
Line: fset.Position(d.Pos()).Line,
}
Expand Down
2 changes: 1 addition & 1 deletion internal/app/godoc_golden_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func readGolden(t *testing.T, path string) string {
func writeGolden(t *testing.T, path, content string) {
t.Helper()

err := os.WriteFile(path, []byte(content), 0o644)
err := os.WriteFile(path, []byte(content), 0o600)
require.NoError(t, err, "failed to write golden file %s", path)

t.Logf("updated golden file: %s", path)
Expand Down
3 changes: 2 additions & 1 deletion internal/app/markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ func FormatMarkdownScanResult(files []MarkdownFile) string {

for _, heading := range file.Headings {
indent := strings.Repeat(" ", heading.Level-1)
result.WriteString(fmt.Sprintf("%s%s %s (line %d)\n",
result.WriteString(fmt.Sprintf(
"%s%s %s (line %d)\n",
indent,
strings.Repeat("#", heading.Level),
heading.Title,
Expand Down
3 changes: 2 additions & 1 deletion internal/app/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ func PrintTree(
"",
path,
ignoreDot,
maxDepth)
maxDepth,
)
if err != nil {
return errors.Wrap(err, "failed to walk directory")
}
Expand Down
57 changes: 36 additions & 21 deletions internal/app/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,25 @@ import (
"github.com/fpt/go-dev-mcp/internal/infra"
)

// Validation check names.
const (
checkGoVet = "go vet"
checkGoBuild = "go build (dry-run)"
checkGoModTidy = "go mod tidy (check)"
checkGofmt = "gofmt check"
)

// Validation result statuses.
const (
statusPass = "pass"
statusFail = "fail"
statusError = "error"
)

// ValidationResult represents the result of a single validation check
type ValidationResult struct {
Check string `json:"check"`
Status string `json:"status"` // "pass", "fail", "error"
Status string `json:"status"` // pass, fail, or error
Output string `json:"output,omitempty"`
Summary string `json:"summary"`
}
Expand All @@ -38,25 +53,25 @@ func ValidateGoCode(ctx context.Context, directory string) (*ValidationReport, e
description string
}{
{
name: "go vet",
name: checkGoVet,
cmd: "go",
args: []string{"vet", "./..."},
description: "Static analysis to find suspicious constructs",
},
{
name: "go build (dry-run)",
name: checkGoBuild,
cmd: "go",
args: []string{"build", "-n", "./..."},
description: "Check if code compiles without building",
},
{
name: "go mod tidy (check)",
name: checkGoModTidy,
cmd: "go",
args: []string{"mod", "tidy", "-diff"},
description: "Check if go.mod is tidy",
},
{
name: "gofmt check",
name: checkGofmt,
cmd: "gofmt",
args: []string{"-l", "."},
description: "Check if code is properly formatted",
Expand All @@ -82,11 +97,11 @@ func ValidateGoCode(ctx context.Context, directory string) (*ValidationReport, e
errors := 0
for _, result := range report.Results {
switch result.Status {
case "pass":
case statusPass:
passed++
case "fail":
case statusFail:
failed++
case "error":
case statusError:
errors++
}
}
Expand All @@ -112,7 +127,7 @@ func ValidateGoCode(ctx context.Context, directory string) (*ValidationReport, e
}

func runValidationCheck(
ctx context.Context,
_ context.Context,
workDir, name, cmdName string,
args []string,
description string,
Expand All @@ -126,12 +141,12 @@ func runValidationCheck(

if err != nil {
// Command couldn't run at all
result.Status = "error"
result.Status = statusError
result.Output = err.Error()
result.Summary = fmt.Sprintf("Could not run %s: %v", name, err)
} else if exitCode != 0 {
// Command ran but returned non-zero exit code
result.Status = "fail"
result.Status = statusFail

// Use stderr for error information, stdout for normal output
if stderr != "" {
Expand All @@ -141,7 +156,7 @@ func runValidationCheck(
}

switch name {
case "gofmt check":
case checkGofmt:
if stdout != "" {
result.Summary = fmt.Sprintf(
"Files need formatting: %s",
Expand All @@ -150,9 +165,9 @@ func runValidationCheck(
} else {
result.Summary = "Files need formatting"
}
case "go mod tidy (check)":
case checkGoModTidy:
result.Summary = "go.mod needs tidying"
case "go vet":
case checkGoVet:
// go vet typically outputs to stderr
issues := stderr
if issues == "" {
Expand All @@ -164,20 +179,20 @@ func runValidationCheck(
} else {
result.Summary = "Vet found issues"
}
case "go build (dry-run)":
case checkGoBuild:
result.Summary = "Build would fail - compilation errors found"
default:
result.Summary = fmt.Sprintf("Check failed: %s", name)
}
} else {
// Command succeeded (exit code 0)
result.Status = "pass"
result.Status = statusPass

switch name {
case "gofmt check":
case checkGofmt:
// gofmt -l returns 0 even when files need formatting, but lists files to stdout
if stdout != "" {
result.Status = "fail"
result.Status = statusFail
result.Output = stdout
result.Summary = fmt.Sprintf(
"Files need formatting: %s",
Expand All @@ -186,12 +201,12 @@ func runValidationCheck(
} else {
result.Summary = "All files are properly formatted"
}
case "go vet":
case checkGoVet:
result.Summary = "No vet issues found"
case "go build (dry-run)":
case checkGoBuild:
// Don't include verbose build output when successful
result.Summary = "Code compiles successfully"
case "go mod tidy (check)":
case checkGoModTidy:
result.Summary = "go.mod is tidy"
default:
if stdout != "" {
Expand Down
9 changes: 6 additions & 3 deletions internal/mcptool/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,8 @@ func getGitHubIssue(
return mcp.NewToolResultError(fmt.Sprintf("Error getting issue: %v", err)), nil
}

comments, _, err := gh.Issues.ListComments(ctx, owner, repo, args.Number,
comments, _, err := gh.Issues.ListComments(
ctx, owner, repo, args.Number,
&github.IssueListCommentsOptions{ListOptions: github.ListOptions{PerPage: 100}},
)
if err != nil {
Expand Down Expand Up @@ -460,7 +461,8 @@ func getGitHubPull(
return mcp.NewToolResultError(fmt.Sprintf("Error getting pull request: %v", err)), nil
}

files, _, err := gh.PullRequests.ListFiles(ctx, owner, repo, args.Number,
files, _, err := gh.PullRequests.ListFiles(
ctx, owner, repo, args.Number,
&github.ListOptions{PerPage: 100},
)
if err != nil {
Expand Down Expand Up @@ -623,7 +625,8 @@ func getGitHubWorkflowRun(
return mcp.NewToolResultError(fmt.Sprintf("Error getting workflow run: %v", err)), nil
}

jobs, _, err := gh.Actions.ListWorkflowJobs(ctx, owner, repo, args.RunID,
jobs, _, err := gh.Actions.ListWorkflowJobs(
ctx, owner, repo, args.RunID,
&github.ListWorkflowJobsOptions{
Filter: "latest",
ListOptions: github.ListOptions{PerPage: 100},
Expand Down
Loading
Loading