a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
118 lines
3.8 KiB
Go
118 lines
3.8 KiB
Go
package mcp
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// QRT-4: the forward direction of the gate — when change_contract flags a
|
|
// change to a heavy symbol, it doesn't just refuse, it hands back the safe
|
|
// path. edit_strategy names a refactoring technique, ranks its complexity
|
|
// impact, and lists the graph-derived safety signals that make the refactor
|
|
// checkable. Refuse + remedy in one reply.
|
|
|
|
// paramCount returns the number of parameters declared on a function/method
|
|
// node — params point at their owner via EdgeParamOf.
|
|
func (s *Server) paramCount(id string) int {
|
|
if s.graph == nil {
|
|
return 0
|
|
}
|
|
n := 0
|
|
for _, e := range s.graph.GetInEdges(id) {
|
|
if e.Kind == graph.EdgeParamOf {
|
|
n++
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
// dominantSymbol picks the function/method in the changed set most worth a
|
|
// refactor — the widest line span, tie-broken by fan-out.
|
|
func (s *Server) dominantSymbol(nodes []*graph.Node) *graph.Node {
|
|
var best *graph.Node
|
|
bestSpan := -1
|
|
for _, n := range nodes {
|
|
if n.Kind != graph.KindFunction && n.Kind != graph.KindMethod {
|
|
continue
|
|
}
|
|
span := n.EndLine - n.StartLine
|
|
if span > bestSpan {
|
|
best = n
|
|
bestSpan = span
|
|
}
|
|
}
|
|
return best
|
|
}
|
|
|
|
func ccImpactTier(lineCount, fanOut int) string {
|
|
switch {
|
|
case lineCount >= 120 || fanOut >= 18:
|
|
return "High"
|
|
case lineCount >= 50 || fanOut >= 9:
|
|
return "Medium"
|
|
default:
|
|
return "Low"
|
|
}
|
|
}
|
|
|
|
// buildEditStrategy derives a named refactoring technique for the dominant
|
|
// changed symbol, or nil when the change is small enough not to warrant one.
|
|
func (s *Server) buildEditStrategy(p *prediction) *editStrategy {
|
|
node := s.dominantSymbol(p.nodes)
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
lineCount := node.EndLine - node.StartLine + 1
|
|
params := s.paramCount(node.ID)
|
|
fanIn, fanOut := computeFanInOut(s.graph, []*graph.Node{node})
|
|
callers := fanIn[node.ID]
|
|
callees := fanOut[node.ID]
|
|
|
|
var technique string
|
|
var steps []string
|
|
switch {
|
|
case params >= 5:
|
|
technique = "Introduce Parameter Object"
|
|
steps = []string{
|
|
fmt.Sprintf("Group the %d parameters of %s into a single options/request struct.", params, node.Name),
|
|
"Add the struct type next to the function and migrate the body to read its fields.",
|
|
fmt.Sprintf("Use change_contract source=edit with the call-site rewrite to confirm no caller of %s breaks before applying.", node.Name),
|
|
}
|
|
case lineCount >= 60 || callees >= 10:
|
|
technique = "Extract Method"
|
|
steps = []string{
|
|
fmt.Sprintf("Identify the %d-line body of %s and pull cohesive blocks into named helpers.", lineCount, node.Name),
|
|
"Keep the public signature stable so callers are unaffected.",
|
|
"Re-run change_contract after each extraction to confirm the blast radius stays bounded.",
|
|
}
|
|
case callers >= 10 && lineCount >= 30:
|
|
technique = "Extract Method"
|
|
steps = []string{
|
|
fmt.Sprintf("%s is %d lines and called from %d sites — extract the stable core into a helper to localise future edits.", node.Name, lineCount, callers),
|
|
"Keep the original as a thin wrapper so existing callers are untouched.",
|
|
}
|
|
default:
|
|
// Small, low-fan-out symbol: an in-place edit is fine, no strategy.
|
|
return nil
|
|
}
|
|
|
|
var safety []string
|
|
if p.impact != nil && len(p.impact.TestFiles) > 0 {
|
|
safety = append(safety, fmt.Sprintf("%d covering test file(s) exercise the changed set", len(p.impact.TestFiles)))
|
|
} else {
|
|
safety = append(safety, "no covering tests found — add one before refactoring")
|
|
}
|
|
safety = append(safety, fmt.Sprintf("%d caller(s) are graph-tracked; use rename_symbol / change_contract source=edit to verify them", callers))
|
|
if p.step != nil && len(p.step.brokenCallers) == 0 {
|
|
safety = append(safety, "current simulation shows no broken callers")
|
|
}
|
|
|
|
return &editStrategy{
|
|
Technique: technique,
|
|
Steps: steps,
|
|
CCImpact: ccImpactTier(lineCount, callees),
|
|
Safety: safety,
|
|
}
|
|
}
|