83 lines
2.1 KiB
Go
83 lines
2.1 KiB
Go
|
|
package tools
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
|
||
|
|
"code.nochebuena.dev/einherjar/mcp/internal/index"
|
||
|
|
"code.nochebuena.dev/einherjar/mcp/internal/rules"
|
||
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||
|
|
)
|
||
|
|
|
||
|
|
type validateSnippetInput struct {
|
||
|
|
Code string `json:"code" jsonschema:"Go source code to validate against Einherjar conventions. A full file is preferred; a partial body will be wrapped automatically."`
|
||
|
|
}
|
||
|
|
|
||
|
|
type validateSnippetOutput struct {
|
||
|
|
Findings []rules.Finding `json:"findings"`
|
||
|
|
Summary string `json:"summary"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func registerValidateSnippet(s *mcp.Server, _ *index.Index) {
|
||
|
|
mcp.AddTool(s, &mcp.Tool{
|
||
|
|
Name: "validate_snippet",
|
||
|
|
Description: "Validate a Go snippet against Einherjar wiring conventions: lifecycle setup, logger configuration, env-var handling, server registration. Findings are advisory, not a substitute for go vet or the project's tests.",
|
||
|
|
}, func(ctx context.Context, req *mcp.CallToolRequest, args validateSnippetInput) (*mcp.CallToolResult, validateSnippetOutput, error) {
|
||
|
|
findings := rules.Run(args.Code)
|
||
|
|
if findings == nil {
|
||
|
|
findings = []rules.Finding{}
|
||
|
|
}
|
||
|
|
summary := summarise(findings)
|
||
|
|
out := validateSnippetOutput{Findings: findings, Summary: summary}
|
||
|
|
return jsonText(out), out, nil
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func summarise(fs []rules.Finding) string {
|
||
|
|
if len(fs) == 0 {
|
||
|
|
return "No issues found — snippet follows Einherjar conventions."
|
||
|
|
}
|
||
|
|
var errs, warns, infos int
|
||
|
|
for _, f := range fs {
|
||
|
|
switch f.Severity {
|
||
|
|
case rules.SeverityError:
|
||
|
|
errs++
|
||
|
|
case rules.SeverityWarning:
|
||
|
|
warns++
|
||
|
|
case rules.SeverityInfo:
|
||
|
|
infos++
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return pluralise(errs, "error", "errors") + ", " +
|
||
|
|
pluralise(warns, "warning", "warnings") + ", " +
|
||
|
|
pluralise(infos, "note", "notes")
|
||
|
|
}
|
||
|
|
|
||
|
|
func pluralise(n int, singular, plural string) string {
|
||
|
|
if n == 1 {
|
||
|
|
return "1 " + singular
|
||
|
|
}
|
||
|
|
return itoa(n) + " " + plural
|
||
|
|
}
|
||
|
|
|
||
|
|
func itoa(n int) string {
|
||
|
|
if n == 0 {
|
||
|
|
return "0"
|
||
|
|
}
|
||
|
|
neg := n < 0
|
||
|
|
if neg {
|
||
|
|
n = -n
|
||
|
|
}
|
||
|
|
var buf [20]byte
|
||
|
|
i := len(buf)
|
||
|
|
for n > 0 {
|
||
|
|
i--
|
||
|
|
buf[i] = byte('0' + n%10)
|
||
|
|
n /= 10
|
||
|
|
}
|
||
|
|
if neg {
|
||
|
|
i--
|
||
|
|
buf[i] = '-'
|
||
|
|
}
|
||
|
|
return string(buf[i:])
|
||
|
|
}
|