nomadcode/services/core/internal/authoring/result.go
toki afb26e67ed feat: complete work item creation sync milestone - slot finalizer, live cycle smoke test, archive completed subtasks
- Add slot_finalizer.go for terminal state management
- Add live cycle smoke test documentation
- Archive completed subtasks (slot terminal state, live cycle smoke)
- Update scheduler jobs and workflow service for roadmap sync
- Update authoring request/result handlers
- Update milestone documentation
2026-06-16 10:06:58 +09:00

108 lines
2.8 KiB
Go

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
}
}