package authoring import ( "fmt" "github.com/nomadcode/nomadcode-core/internal/projectsync" "github.com/nomadcode/nomadcode-core/internal/roadmapsync" ) // DecisionInput contains the outputs and signals from the authoring process, // used to determine the final authoring run state. type DecisionInput struct { BridgeSuccess bool DevelopMatch roadmapsync.ProjectionCandidate ExitCode *int PushFailed bool DirtyWorkspace bool Conflict bool } // Decision represents the state, reason, and failure category determined by the result helper. type Decision struct { State string // "in_progress", "succeeded", "failed" Reason string FailureCategory string // "bridge_failed", "exit_code", "push_failed", "dirty_workspace", "conflict" } // DecideAuthoringResult evaluates the authoring run inputs and signals to classify // the task outcome as succeeded, failed (with specific category), or in_progress. func DecideAuthoringResult(in DecisionInput) Decision { if !in.BridgeSuccess { return Decision{ State: "failed", Reason: "model bridge response failed", FailureCategory: "bridge_failed", } } if in.ExitCode != nil && *in.ExitCode != 0 { return Decision{ State: "failed", Reason: fmt.Sprintf("agent process exited with non-zero code %d", *in.ExitCode), FailureCategory: "exit_code", } } if in.PushFailed { return Decision{ State: "failed", Reason: "git push to develop branch failed", FailureCategory: "push_failed", } } if in.DirtyWorkspace { return Decision{ State: "failed", Reason: "workspace contains dirty changes", FailureCategory: "dirty_workspace", } } if in.Conflict { return Decision{ State: "failed", Reason: "git merge conflict detected", FailureCategory: "conflict", } } if in.DevelopMatch.Ready { reason := in.DevelopMatch.Reason if reason == "" { reason = "pushed develop milestone matched expected identity" } return Decision{ State: "succeeded", Reason: reason, } } reason := in.DevelopMatch.Reason if reason == "" { reason = "waiting for pushed develop milestone match" } return Decision{ State: "in_progress", Reason: reason, } } // SlotStateForAuthoringOutcome maps terminal authoring outcomes to the slot // state the scheduler should persist. Non-terminal decisions are not applied. func SlotStateForAuthoringOutcome(decision Decision) (projectsync.SlotState, bool) { switch decision.State { case "succeeded": return projectsync.SlotStateAvailable, true case "failed": switch decision.FailureCategory { case "dirty_workspace", "conflict", "push_failed": return projectsync.SlotStateDirty, true default: return projectsync.SlotStateError, true } default: return "", false } }