feat(dispatch): QA gate before auto-PR
After agent completes, run build + vet + test before creating PR. If QA fails, mark workspace as failed with "QA check failed" — bad code never gets PR'd. Supports Go (build/vet/test), PHP (composer install/test), and Node (npm install/test). Unknown languages pass through. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
7e30f336cc
commit
d6864eb37c
1 changed files with 65 additions and 2 deletions
|
|
@ -4,6 +4,7 @@ package agentic
|
|||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
|
|
@ -191,8 +192,19 @@ func (s *PrepSubsystem) spawnAgent(agent, prompt, wsDir string) (int, string, er
|
|||
}
|
||||
|
||||
if finalStatus == "completed" {
|
||||
s.autoCreatePR(wsDir)
|
||||
s.autoVerifyAndMerge(wsDir)
|
||||
// Run QA before PR — if QA fails, mark as failed, don't PR
|
||||
if !s.runQA(wsDir) {
|
||||
finalStatus = "failed"
|
||||
question = "QA check failed — build or tests did not pass"
|
||||
if st, stErr := readStatus(wsDir); stErr == nil {
|
||||
st.Status = finalStatus
|
||||
st.Question = question
|
||||
writeStatus(wsDir, st)
|
||||
}
|
||||
} else {
|
||||
s.autoCreatePR(wsDir)
|
||||
s.autoVerifyAndMerge(wsDir)
|
||||
}
|
||||
}
|
||||
|
||||
s.ingestFindings(wsDir)
|
||||
|
|
@ -202,6 +214,57 @@ func (s *PrepSubsystem) spawnAgent(agent, prompt, wsDir string) (int, string, er
|
|||
return pid, outputFile, nil
|
||||
}
|
||||
|
||||
// runQA runs build + test checks on the repo after agent completion.
|
||||
// Returns true if QA passes, false if build or tests fail.
|
||||
func (s *PrepSubsystem) runQA(wsDir string) bool {
|
||||
repoDir := core.JoinPath(wsDir, "repo")
|
||||
|
||||
// Detect language and run appropriate checks
|
||||
if fs.IsFile(core.JoinPath(repoDir, "go.mod")) {
|
||||
// Go: build + vet + test
|
||||
for _, args := range [][]string{
|
||||
{"go", "build", "./..."},
|
||||
{"go", "vet", "./..."},
|
||||
{"go", "test", "./...", "-count=1", "-timeout", "120s"},
|
||||
} {
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Dir = repoDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
core.Warn("QA failed", "cmd", core.Join(" ", args...), "err", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if fs.IsFile(core.JoinPath(repoDir, "composer.json")) {
|
||||
// PHP: composer install + test
|
||||
install := exec.Command("composer", "install", "--no-interaction")
|
||||
install.Dir = repoDir
|
||||
if err := install.Run(); err != nil {
|
||||
return false
|
||||
}
|
||||
test := exec.Command("composer", "test")
|
||||
test.Dir = repoDir
|
||||
return test.Run() == nil
|
||||
}
|
||||
|
||||
if fs.IsFile(core.JoinPath(repoDir, "package.json")) {
|
||||
// Node: npm install + test
|
||||
install := exec.Command("npm", "install")
|
||||
install.Dir = repoDir
|
||||
if err := install.Run(); err != nil {
|
||||
return false
|
||||
}
|
||||
test := exec.Command("npm", "test")
|
||||
test.Dir = repoDir
|
||||
return test.Run() == nil
|
||||
}
|
||||
|
||||
// Unknown language — pass QA (no checks to run)
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest, input DispatchInput) (*mcp.CallToolResult, DispatchOutput, error) {
|
||||
if input.Repo == "" {
|
||||
return nil, DispatchOutput{}, core.E("dispatch", "repo is required", nil)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue