Skip to content
Open
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
15 changes: 13 additions & 2 deletions go/internal/fast/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ type buildInput struct {
RepoURL string `json:"repo_url"`
ArtifactsDir string `json:"artifacts_dir"`
AdditionalContext string `json:"additional_context"`
WorkBranch string `json:"work_branch"`
Config map[string]any `json:"config"`
}

Expand Down Expand Up @@ -196,20 +197,30 @@ func Build(ctx context.Context, deps *Deps, input map[string]any) (any, error) {
// ── 1. GIT INIT (1 attempt, non-fatal) ──────────────────────────────────
deps.note(ctx, "Fast build: git init", "fast_build", "git_init")
var gitConfig map[string]any
rawGit, gitErr := deps.Call(ctx, node+".run_git_init", map[string]any{
gitInput := map[string]any{
"repo_path": repoPath,
"goal": in.Goal,
"artifacts_dir": absArtifactsDir,
"model": resolved["git_model"],
"permission_mode": cfg.PermissionMode,
"ai_provider": aiProvider,
"build_id": "",
})
}
if in.WorkBranch != "" {
gitInput["work_branch"] = in.WorkBranch
}
rawGit, gitErr := deps.Call(ctx, node+".run_git_init", gitInput)
if gitErr != nil {
if in.WorkBranch != "" {
return nil, fmt.Errorf("work branch %q could not be initialized: %w", in.WorkBranch, gitErr)
}
deps.note(ctx, fmt.Sprintf("Git init exception (non-fatal): %s", gitErr),
"fast_build", "git_init", "error")
} else {
gitInit := rawGit
if in.WorkBranch != "" && (!mapBool(gitInit, "success") || getString(gitInit, "integration_branch", "") != in.WorkBranch) {
return nil, fmt.Errorf("work branch %q does not exist on the remote or could not be checked out", in.WorkBranch)
}
if mapBool(gitInit, "success") {
gitConfig = map[string]any{
"integration_branch": gitInit["integration_branch"],
Expand Down
63 changes: 62 additions & 1 deletion go/internal/orch/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ func ResolveHandler(ctx context.Context, deps *Deps, input map[string]any) (any,
return nil, err
}
cfg.EnableGithubPR = false // the PR already exists — never create
permissionMode := cfg.PermissionMode
if permissionMode == "" {
permissionMode = "auto"
}

if in.PRNumber == 0 || in.HeadBranch == "" || in.RepoURL == "" || in.PRURL == "" {
return nil, errors.New(
Expand Down Expand Up @@ -135,6 +139,7 @@ func ResolveHandler(ctx context.Context, deps *Deps, input map[string]any) (any,
resolverModel = "sonnet"
}

remoteBefore := remoteBranchSHA(ctx, repoPath, in.HeadBranch)
resolveResult, err := deps.Call(ctx, "run_pr_resolver", map[string]any{
"repo_path": repoPath,
"pr_number": in.PRNumber,
Expand All @@ -148,7 +153,7 @@ func ResolveHandler(ctx context.Context, deps *Deps, input map[string]any) (any,
"goal": in.Goal,
"additional_context": in.AdditionalContext,
"model": resolverModel,
"permission_mode": cfg.PermissionMode,
"permission_mode": permissionMode,
"ai_provider": cfg.AIProvider(),
}, "run_pr_resolver")
if err != nil {
Expand All @@ -170,6 +175,19 @@ func ResolveHandler(ctx context.Context, deps *Deps, input map[string]any) (any,
"resolve", "push", "error")
}
}
if resolverReportInvalid(resolveResult) {
remoteAfter := remoteBranchSHA(ctx, repoPath, in.HeadBranch)
if remoteBefore != "" && remoteAfter != "" && remoteBefore != remoteAfter {
resolveResult["pushed"] = true
resolveResult["fixed"] = true
resolveResult["commit_shas"] = remoteCommitSHAs(ctx, repoPath, remoteBefore, remoteAfter)
resolveResult["files_changed"] = remoteFilesChanged(ctx, repoPath, remoteBefore, remoteAfter)
resolveResult["summary"] = "work pushed; agent report invalid"
resolveResult["error_message"] = "agent report invalid; verified work pushed to the remote branch"
pushed = true
deps.Note(ctx, "Resolve: agent report invalid, but verified work pushed to the remote branch", "resolve", "report", "warning")
}
}

// Capture the new HEAD SHA after push so the CI watcher can anchor verdicts
// to this specific commit (avoids the previous HEAD's stale check states).
Expand Down Expand Up @@ -264,6 +282,49 @@ func ResolveHandler(ctx context.Context, deps *Deps, input map[string]any) (any,
}, nil
}

func resolverReportInvalid(result map[string]any) bool {
return mapStr(result, "error_message", "") == "PR resolver agent failed to produce a valid result." ||
mapStr(result, "summary", "") == "PR resolver agent failed to produce a valid result."
}

func remoteBranchSHA(ctx context.Context, repoPath, branch string) string {
r := runGit(ctx, repoPath, "ls-remote", "origin", "refs/heads/"+branch)
if r.ExitCode != 0 {
return ""
}
fields := strings.Fields(r.Stdout)
if len(fields) == 0 {
return ""
}
return fields[0]
}

func remoteCommitSHAs(ctx context.Context, repoPath, before, after string) []string {
r := runGit(ctx, repoPath, "rev-list", "--reverse", before+".."+after)
if r.ExitCode != 0 {
return []string{}
}
return nonEmptyLines(r.Stdout)
}

func remoteFilesChanged(ctx context.Context, repoPath, before, after string) []string {
r := runGit(ctx, repoPath, "diff", "--name-only", before, after)
if r.ExitCode != 0 {
return []string{}
}
return nonEmptyLines(r.Stdout)
}

func nonEmptyLines(s string) []string {
var out []string
for _, line := range strings.Split(s, "\n") {
if line = strings.TrimSpace(line); line != "" {
out = append(out, line)
}
}
return out
}

// attemptBaseMerge fetches base_branch and merges it into the current branch.
// Ports _attempt_base_merge (app.py:1960). Returns (merge_state, conflicted)
// where merge_state is "clean" (already up to date), "merged" (merge succeeded),
Expand Down
3 changes: 3 additions & 0 deletions go/internal/prompts/advisor/pr_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ func PRResolverTaskPrompt(opts PRResolverTaskOptions) string {
taskLines = append(taskLines,
strconv.Itoa(step)+". Re-run failing tests locally to confirm they pass.")
step++
taskLines = append(taskLines, "Before committing, run the test suite for every package or module touched, plus gofmt and repository-standard linters where applicable. Do NOT push if any affected test or required check fails; report every test command and outcome in the result.")
taskLines = append(taskLines,
strconv.Itoa(step)+". Commit + `git push origin "+opts.HeadBranch+"` — do NOT create "+
"a new PR.")
Expand Down Expand Up @@ -171,6 +172,8 @@ push. The PR already exists — do NOT create a new one.
branch).
5. You have re-run the relevant tests locally and they pass.

Before committing, you MUST run the test suite for every package or module you touched, plus gofmt and any repository-standard linters where applicable. You MUST NOT push if any affected test or required check fails. Report every test command and its outcome in your result.

## ABSOLUTELY FORBIDDEN — these are workarounds, not fixes

You MUST NOT do any of the following to make CI green or to "address" a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@ Keep API stable.
3. Fix every failing CI check by changing PRODUCTION code (no silenced tests).
4. Address every actionable review comment, recording each one in `addressed_comments` (true/false + brief note).
5. Re-run failing tests locally to confirm they pass.
Before committing, run the test suite for every package or module touched, plus gofmt and repository-standard linters where applicable. Do NOT push if any affected test or required check fails; report every test command and outcome in the result.
6. Commit + `git push origin feature` — do NOT create a new PR.
7. Return a `PRResolveResult` JSON object.
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ The orchestrator already merged `origin/main` into the head branch with no confl
2. Fix every failing CI check by changing PRODUCTION code (no silenced tests).
3. Address every actionable review comment, recording each one in `addressed_comments` (true/false + brief note).
4. Re-run failing tests locally to confirm they pass.
Before committing, run the test suite for every package or module touched, plus gofmt and repository-standard linters where applicable. Do NOT push if any affected test or required check fails; report every test command and outcome in the result.
5. Commit + `git push origin feature` — do NOT create a new PR.
6. Return a `PRResolveResult` JSON object.
2 changes: 2 additions & 0 deletions go/internal/prompts/advisor/testdata/pr_resolver_system.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ push. The PR already exists — do NOT create a new one.
branch).
5. You have re-run the relevant tests locally and they pass.

Before committing, you MUST run the test suite for every package or module you touched, plus gofmt and any repository-standard linters where applicable. You MUST NOT push if any affected test or required check fails. Report every test command and its outcome in your result.

## ABSOLUTELY FORBIDDEN — these are workarounds, not fixes

You MUST NOT do any of the following to make CI green or to "address" a
Expand Down
13 changes: 10 additions & 3 deletions go/internal/prompts/gitops/git_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,10 @@ const GitInitSystemPrompt = "You are a DevOps engineer setting up a git-based fe

// GitInitOptions carries the arguments for GitInitTaskPrompt.
type GitInitOptions struct {
RepoPath string
Goal string
BuildID string
RepoPath string
Goal string
BuildID string
WorkBranch string
}

// GitInitTaskPrompt builds the task prompt for the git initialization agent
Expand All @@ -104,6 +105,9 @@ func GitInitTaskPrompt(opts GitInitOptions) string {
if opts.BuildID != "" {
sections = append(sections, fmt.Sprintf("- **Build ID**: `%s` (prefix integration branch slug with this)", opts.BuildID))
}
if opts.WorkBranch != "" {
sections = append(sections, fmt.Sprintf("- **Existing work branch**: `%s` (fetch and check out this remote branch; do not create a new integration branch)", opts.WorkBranch))
}

sections = append(sections, "\n## Your Task\n"+
"1. Check if `.git` exists in the repository path.\n"+
Expand All @@ -113,6 +117,9 @@ func GitInitTaskPrompt(opts GitInitOptions) string {
"5. Create the `.worktrees/` directory and ensure it's in `.gitignore`.\n"+
"6. Detect the remote origin URL and default branch (if any).\n"+
"7. Return a GitInitResult JSON object.")
if opts.WorkBranch != "" {
sections = append(sections, fmt.Sprintf("\n## Existing Work Branch\nFetch `origin/%s` and check it out directly as the integration branch. Do not create a new branch. If it does not exist on the remote, return `success=false` with a clear error before any planning or coding work.", opts.WorkBranch))
}

return strings.Join(sections, "\n")
}
8 changes: 5 additions & 3 deletions go/internal/roles/gitops/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type gitInitInput struct {
AIProvider string `json:"ai_provider"`
PreviousError string `json:"previous_error"`
BuildID string `json:"build_id"`
WorkBranch string `json:"work_branch"`
}

// RunGitInit initializes the git repo and creates the integration branch.
Expand All @@ -40,9 +41,10 @@ func RunGitInit(ctx context.Context, deps *Deps, input map[string]any) (any, err
deps.App.Note(ctx, fmt.Sprintf("Git init starting for: %s", truncateRunes(in.Goal, 80)), "git_init", "start")

taskPrompt := gitprompts.GitInitTaskPrompt(gitprompts.GitInitOptions{
RepoPath: in.RepoPath,
Goal: in.Goal,
BuildID: in.BuildID,
RepoPath: in.RepoPath,
Goal: in.Goal,
BuildID: in.BuildID,
WorkBranch: in.WorkBranch,
})

// Build system prompt with error context if retrying.
Expand Down
Loading