(workflow) add simple harness workflow for manual testing
This commit is contained in:
+91
-1
@@ -1,3 +1,93 @@
|
|||||||
package action
|
package action
|
||||||
|
|
||||||
// Empty stub - will be filled in T0.5
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/rockliang/poimen/workflows/action/llm"
|
||||||
|
"github.com/rockliang/poimen/workflows/prompts"
|
||||||
|
"github.com/rockliang/poimen/workflows/statemachine"
|
||||||
|
"go.temporal.io/sdk/activity"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ImplementerInput is input to ImplementerActivity.
|
||||||
|
type ImplementerInput struct {
|
||||||
|
Config statemachine.OrchestratorConfig
|
||||||
|
TaskID string
|
||||||
|
WorktreePath string
|
||||||
|
Lessons string // "known errors — do not repeat" section
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementerOutput is the output of ImplementerActivity.
|
||||||
|
type ImplementerOutput struct {
|
||||||
|
Success bool
|
||||||
|
Changes string // summary of changes made
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementerActivity calls the Implementer LLM to implement the task.
|
||||||
|
func ImplementerActivity(ctx context.Context, in ImplementerInput) (ImplementerOutput, error) {
|
||||||
|
// Record heartbeat
|
||||||
|
activity.RecordHeartbeat(ctx, "starting implementer for "+in.TaskID)
|
||||||
|
|
||||||
|
// Get LLM client
|
||||||
|
client, err := llm.NewClient()
|
||||||
|
if err != nil {
|
||||||
|
return ImplementerOutput{}, fmt.Errorf("failed to create LLM client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implementer spec
|
||||||
|
implementerSpec, exists := in.Config.RolePrompts["implementer"]
|
||||||
|
if !exists {
|
||||||
|
return ImplementerOutput{}, fmt.Errorf("implementer role prompt not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build variables for template
|
||||||
|
templateVars := map[string]any{
|
||||||
|
"SystemPrompt": in.Config.SystemPrompt,
|
||||||
|
"Task": in.TaskID,
|
||||||
|
"WorktreePath": in.WorktreePath,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject lessons if provided
|
||||||
|
if in.Lessons != "" {
|
||||||
|
templateVars["Lessons"] = in.Lessons
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render template
|
||||||
|
var templateContent string
|
||||||
|
if implementerSpec.RawTemplate != "" {
|
||||||
|
templateContent = implementerSpec.RawTemplate
|
||||||
|
} else {
|
||||||
|
// Parse and render the embedded template
|
||||||
|
templateContent, err = prompts.Render(implementerSpec.TemplateRef, templateVars)
|
||||||
|
if err != nil {
|
||||||
|
return ImplementerOutput{}, fmt.Errorf("failed to render implementer template: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call LLM
|
||||||
|
messages := []llm.MessageParam{
|
||||||
|
{
|
||||||
|
Role: "user",
|
||||||
|
Content: templateContent,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.CreateMessage(ctx, llm.MessageInput{
|
||||||
|
Model: implementerSpec.Model,
|
||||||
|
SystemPrompt: in.Config.SystemPrompt,
|
||||||
|
Messages: messages,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return ImplementerOutput{}, fmt.Errorf("implementer LLM call failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record progress
|
||||||
|
activity.RecordHeartbeat(ctx, "implementer completed for "+in.TaskID)
|
||||||
|
|
||||||
|
// Return success (in full implementation would parse response and execute tool calls)
|
||||||
|
return ImplementerOutput{
|
||||||
|
Success: true,
|
||||||
|
Changes: response,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,44 @@
|
|||||||
package action
|
package action
|
||||||
|
|
||||||
// Empty stub - will be filled in later
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RunIntegrationTestInput is input to RunIntegrationTestActivity.
|
||||||
|
type RunIntegrationTestInput struct {
|
||||||
|
WorktreePath string
|
||||||
|
TestCmd string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunIntegrationTestOutput is the output of RunIntegrationTestActivity.
|
||||||
|
type RunIntegrationTestOutput struct {
|
||||||
|
Passed bool
|
||||||
|
Logs string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunIntegrationTestActivity runs integration tests in the worktree.
|
||||||
|
func RunIntegrationTestActivity(ctx context.Context, in RunIntegrationTestInput) (RunIntegrationTestOutput, error) {
|
||||||
|
if in.TestCmd == "" {
|
||||||
|
// No test command, assume pass
|
||||||
|
return RunIntegrationTestOutput{Passed: true, Logs: "No test command provided"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run test command
|
||||||
|
cmd := exec.CommandContext(ctx, "sh", "-c", in.TestCmd)
|
||||||
|
cmd.Dir = in.WorktreePath
|
||||||
|
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return RunIntegrationTestOutput{
|
||||||
|
Passed: false,
|
||||||
|
Logs: string(output) + "\nError: " + err.Error(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return RunIntegrationTestOutput{
|
||||||
|
Passed: true,
|
||||||
|
Logs: string(output),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
+76
-1
@@ -1,3 +1,78 @@
|
|||||||
package action
|
package action
|
||||||
|
|
||||||
// Empty stub - will be filled in T0.5
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/rockliang/poimen/workflows/action/llm"
|
||||||
|
"github.com/rockliang/poimen/workflows/prompts"
|
||||||
|
"github.com/rockliang/poimen/workflows/statemachine"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JudgeInput is input to JudgeActivity.
|
||||||
|
type JudgeInput struct {
|
||||||
|
Config statemachine.OrchestratorConfig
|
||||||
|
Diff string // git diff output
|
||||||
|
IntegrationTestLogs string // test output
|
||||||
|
}
|
||||||
|
|
||||||
|
// JudgeOutput is the output of JudgeActivity.
|
||||||
|
type JudgeOutput struct {
|
||||||
|
Verdict string // "pass" or "fail"
|
||||||
|
Critique string // explanation if fail
|
||||||
|
}
|
||||||
|
|
||||||
|
// JudgeActivity calls the Judge LLM to review correctness.
|
||||||
|
func JudgeActivity(ctx context.Context, in JudgeInput) (JudgeOutput, error) {
|
||||||
|
// Get LLM client
|
||||||
|
client, err := llm.NewClient()
|
||||||
|
if err != nil {
|
||||||
|
return JudgeOutput{}, fmt.Errorf("failed to create LLM client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get judge spec
|
||||||
|
judgeSpec, exists := in.Config.RolePrompts["judge"]
|
||||||
|
if !exists {
|
||||||
|
return JudgeOutput{}, fmt.Errorf("judge role prompt not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render template
|
||||||
|
var templateContent string
|
||||||
|
if judgeSpec.RawTemplate != "" {
|
||||||
|
templateContent = judgeSpec.RawTemplate
|
||||||
|
} else {
|
||||||
|
// Parse and render the embedded template
|
||||||
|
templateContent, err = prompts.Render(judgeSpec.TemplateRef, map[string]any{
|
||||||
|
"SystemPrompt": in.Config.SystemPrompt,
|
||||||
|
"Diff": in.Diff,
|
||||||
|
"TestResult": in.IntegrationTestLogs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return JudgeOutput{}, fmt.Errorf("failed to render judge template: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call LLM
|
||||||
|
messages := []llm.MessageParam{
|
||||||
|
{
|
||||||
|
Role: "user",
|
||||||
|
Content: templateContent,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.CreateMessage(ctx, llm.MessageInput{
|
||||||
|
Model: judgeSpec.Model,
|
||||||
|
SystemPrompt: in.Config.SystemPrompt,
|
||||||
|
Messages: messages,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return JudgeOutput{}, fmt.Errorf("judge LLM call failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now, return a default pass verdict
|
||||||
|
// In full implementation, would parse LLM response
|
||||||
|
return JudgeOutput{
|
||||||
|
Verdict: "pass",
|
||||||
|
Critique: response,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
+95
-1
@@ -1,3 +1,97 @@
|
|||||||
package action
|
package action
|
||||||
|
|
||||||
// Empty stub - will be filled in later
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Lesson represents a learned lesson from a failed attempt.
|
||||||
|
type Lesson struct {
|
||||||
|
Attempt int `json:"attempt"`
|
||||||
|
Critique string `json:"critique"`
|
||||||
|
FailedApproachSummary string `json:"failed_approach_summary"`
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadLessonsInput is input to ReadLessonsActivity.
|
||||||
|
type ReadLessonsInput struct {
|
||||||
|
TargetRepoPath string
|
||||||
|
TaskID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadLessonsOutput is the output of ReadLessonsActivity.
|
||||||
|
type ReadLessonsOutput struct {
|
||||||
|
Lessons string // JSONL or formatted string of lessons
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadLessonsActivity reads lessons for a task.
|
||||||
|
func ReadLessonsActivity(ctx context.Context, in ReadLessonsInput) (ReadLessonsOutput, error) {
|
||||||
|
lessonsFile := filepath.Join(in.TargetRepoPath, "tasks", ".orchestrator", "lessons", in.TaskID+".jsonl")
|
||||||
|
|
||||||
|
// If file doesn't exist, return empty lessons
|
||||||
|
if _, err := os.Stat(lessonsFile); os.IsNotExist(err) {
|
||||||
|
return ReadLessonsOutput{Lessons: ""}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and format lessons
|
||||||
|
data, err := os.ReadFile(lessonsFile)
|
||||||
|
if err != nil {
|
||||||
|
return ReadLessonsOutput{}, fmt.Errorf("failed to read lessons file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format lessons as plain text
|
||||||
|
lines := string(data)
|
||||||
|
return ReadLessonsOutput{Lessons: lines}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLessonsInput is input to UpdateLessonsActivity.
|
||||||
|
type UpdateLessonsInput struct {
|
||||||
|
TargetRepoPath string
|
||||||
|
TaskID string
|
||||||
|
Attempt int
|
||||||
|
Critique string
|
||||||
|
FailedApproachSummary string
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLessonsActivity appends a lesson to the lessons file.
|
||||||
|
func UpdateLessonsActivity(ctx context.Context, in UpdateLessonsInput) error {
|
||||||
|
lessonsDir := filepath.Join(in.TargetRepoPath, "tasks", ".orchestrator", "lessons")
|
||||||
|
|
||||||
|
// Create directory if it doesn't exist
|
||||||
|
if err := os.MkdirAll(lessonsDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create lessons directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lessonsFile := filepath.Join(lessonsDir, in.TaskID+".jsonl")
|
||||||
|
|
||||||
|
// Create lesson entry
|
||||||
|
lesson := map[string]any{
|
||||||
|
"attempt": in.Attempt,
|
||||||
|
"critique": in.Critique,
|
||||||
|
"failed_approach_summary": in.FailedApproachSummary,
|
||||||
|
"timestamp": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal to JSON
|
||||||
|
data, err := json.Marshal(lesson)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal lesson: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append to file
|
||||||
|
f, err := os.OpenFile(lessonsFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open lessons file: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
_, err = f.Write(append(data, '\n'))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write lesson: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
+50
-1
@@ -1,3 +1,52 @@
|
|||||||
package llm
|
package llm
|
||||||
|
|
||||||
// Empty stub - will be filled in T0.5
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/rockliang/poimen/workflows/statemachine"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnthropicClient is a thin wrapper around the Anthropic API.
|
||||||
|
type AnthropicClient struct {
|
||||||
|
apiKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a new AnthropicClient from the ANTHROPIC_API_KEY env var.
|
||||||
|
func NewClient() (*AnthropicClient, error) {
|
||||||
|
apiKey := os.Getenv("ANTHROPIC_API_KEY")
|
||||||
|
if apiKey == "" {
|
||||||
|
return nil, fmt.Errorf("ANTHROPIC_API_KEY environment variable not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AnthropicClient{
|
||||||
|
apiKey: apiKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageInput is the input to CreateMessage.
|
||||||
|
type MessageInput struct {
|
||||||
|
Model statemachine.ModelSpec
|
||||||
|
SystemPrompt string
|
||||||
|
Messages []MessageParam
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageParam represents a message parameter (simplified).
|
||||||
|
type MessageParam struct {
|
||||||
|
Role string
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMessage calls the Anthropic API and returns the response text.
|
||||||
|
// Note: This is a stub implementation that would be fully implemented with actual API calls.
|
||||||
|
func (c *AnthropicClient) CreateMessage(ctx context.Context, in MessageInput) (string, error) {
|
||||||
|
if c.apiKey == "" {
|
||||||
|
return "", fmt.Errorf("API key not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder implementation
|
||||||
|
// In a real implementation, this would call the Anthropic API
|
||||||
|
// For now, we return a mock response to allow testing
|
||||||
|
return fmt.Sprintf("Mock response for model %s: Processing request with %d messages", in.Model.ModelID, len(in.Messages)), nil
|
||||||
|
}
|
||||||
|
|||||||
+85
-1
@@ -1,3 +1,87 @@
|
|||||||
package action
|
package action
|
||||||
|
|
||||||
// Empty stub - will be filled in T0.5
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/rockliang/poimen/workflows/action/llm"
|
||||||
|
"github.com/rockliang/poimen/workflows/prompts"
|
||||||
|
"github.com/rockliang/poimen/workflows/statemachine"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PlanningInput is input to PlanningActivity.
|
||||||
|
type PlanningInput struct {
|
||||||
|
Config statemachine.OrchestratorConfig
|
||||||
|
BoardState string // JSON or markdown of task board
|
||||||
|
Milestone string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskDispatch represents a dispatched task.
|
||||||
|
type TaskDispatch struct {
|
||||||
|
TaskID string
|
||||||
|
PromptSpec statemachine.PromptSpec
|
||||||
|
BaseTimeout *int64 // optional override in milliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlanningOutput is the output of PlanningActivity.
|
||||||
|
type PlanningOutput struct {
|
||||||
|
Tasks []TaskDispatch
|
||||||
|
SubmilestoneComplete bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlanningActivity calls the Planner LLM to decide which tasks to dispatch.
|
||||||
|
func PlanningActivity(ctx context.Context, in PlanningInput) (PlanningOutput, error) {
|
||||||
|
// Get LLM client
|
||||||
|
client, err := llm.NewClient()
|
||||||
|
if err != nil {
|
||||||
|
return PlanningOutput{}, fmt.Errorf("failed to create LLM client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get planner spec
|
||||||
|
plannerSpec, exists := in.Config.RolePrompts["planner"]
|
||||||
|
if !exists {
|
||||||
|
return PlanningOutput{}, fmt.Errorf("planner role prompt not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render template
|
||||||
|
var templateContent string
|
||||||
|
if plannerSpec.RawTemplate != "" {
|
||||||
|
templateContent = plannerSpec.RawTemplate
|
||||||
|
} else {
|
||||||
|
// Parse and render the embedded template
|
||||||
|
templateContent, err = prompts.Render(plannerSpec.TemplateRef, map[string]any{
|
||||||
|
"SystemPrompt": in.Config.SystemPrompt,
|
||||||
|
"BoardState": in.BoardState,
|
||||||
|
"Milestone": in.Milestone,
|
||||||
|
"Config": in.Config,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return PlanningOutput{}, fmt.Errorf("failed to render planner template: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call LLM
|
||||||
|
messages := []llm.MessageParam{
|
||||||
|
{
|
||||||
|
Role: "user",
|
||||||
|
Content: templateContent,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.CreateMessage(ctx, llm.MessageInput{
|
||||||
|
Model: plannerSpec.Model,
|
||||||
|
SystemPrompt: in.Config.SystemPrompt,
|
||||||
|
Messages: messages,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return PlanningOutput{}, fmt.Errorf("planner LLM call failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now, return empty dispatch (will be parsed from LLM response in full implementation)
|
||||||
|
// This is a stub that allows the test to verify the activity is called
|
||||||
|
_ = response
|
||||||
|
return PlanningOutput{
|
||||||
|
Tasks: []TaskDispatch{},
|
||||||
|
SubmilestoneComplete: false,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
+161
-1
@@ -1,3 +1,163 @@
|
|||||||
package action
|
package action
|
||||||
|
|
||||||
// Empty stub - will be filled in T0.4
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.temporal.io/sdk/activity"
|
||||||
|
"github.com/rockliang/poimen/workflows/statemachine"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrepareSkillsInput is input to PrepareSkillsActivity.
|
||||||
|
type PrepareSkillsInput struct {
|
||||||
|
Skills []statemachine.SkillRef
|
||||||
|
StreamTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareSkillsActivity prepares skills for use via pi command.
|
||||||
|
func PrepareSkillsActivity(ctx context.Context, in PrepareSkillsInput) error {
|
||||||
|
for _, skill := range in.Skills {
|
||||||
|
activity.RecordHeartbeat(ctx, skill.Name)
|
||||||
|
|
||||||
|
// Run: pi clone-or-fetch <skill-url> --stream-timeout=<duration>
|
||||||
|
cmd := exec.CommandContext(ctx, "pi", "clone-or-fetch", skill.URL, fmt.Sprintf("--stream-timeout=%s", in.StreamTimeout.String()))
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
// Classify error
|
||||||
|
classifiedErr := ClassifyPiErr(err, skill.Name)
|
||||||
|
return classifiedErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClassifyPiErr classifies pi command errors into buckets.
|
||||||
|
// 4xx → NonRetryableApplicationError "PiClientError"
|
||||||
|
// 504 → ApplicationError "PiStreamTimeout" (retryable, but orchestrator learns and doubles timeout)
|
||||||
|
// 5xx (except 504) → generic retryable error
|
||||||
|
// Other → retryable
|
||||||
|
func ClassifyPiErr(err error, skillName string) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to extract HTTP status code from error message
|
||||||
|
statusCode := ExtractHTTPStatus(err)
|
||||||
|
if statusCode == 0 {
|
||||||
|
// Not an HTTP error, return as-is for generic retry
|
||||||
|
return fmt.Errorf("pi %s failed: %w", skillName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case statusCode >= 400 && statusCode < 500:
|
||||||
|
// 4xx errors are non-retryable
|
||||||
|
return NewNonRetryableApplicationError(
|
||||||
|
fmt.Sprintf("PiClientError: status %d", statusCode),
|
||||||
|
fmt.Sprintf("pi %s returned HTTP %d", skillName, statusCode),
|
||||||
|
)
|
||||||
|
case statusCode == 504:
|
||||||
|
// 504 Gateway Timeout - stream timeout
|
||||||
|
// This is retryable, but signals the orchestrator to double the timeout
|
||||||
|
return NewApplicationError(
|
||||||
|
fmt.Sprintf("PiStreamTimeout: status %d", statusCode),
|
||||||
|
fmt.Sprintf("pi %s returned HTTP 504 (stream timeout)", skillName),
|
||||||
|
)
|
||||||
|
case statusCode >= 500:
|
||||||
|
// Other 5xx errors are retryable
|
||||||
|
return fmt.Errorf("pi %s returned HTTP %d: %w", skillName, statusCode, err)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("pi %s failed: %w", skillName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractHTTPStatus tries to extract HTTP status code from error message.
|
||||||
|
func ExtractHTTPStatus(err error) int {
|
||||||
|
if err == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
errStr := err.Error()
|
||||||
|
|
||||||
|
// Try to find 3-digit numbers that could be HTTP status codes
|
||||||
|
parts := strings.Fields(errStr)
|
||||||
|
for i, part := range parts {
|
||||||
|
// Check if part is a 3-digit number (HTTP status code)
|
||||||
|
if len(part) >= 3 {
|
||||||
|
code, err := strconv.Atoi(part[:3])
|
||||||
|
if err == nil && code >= 100 && code < 600 {
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if previous part is "status" or "HTTP"
|
||||||
|
if i > 0 {
|
||||||
|
prev := strings.ToLower(parts[i-1])
|
||||||
|
if (prev == "status" || prev == "status:") && len(part) >= 3 {
|
||||||
|
code, err := strconv.Atoi(part[:3])
|
||||||
|
if err == nil && code >= 100 && code < 600 {
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNonRetryableApplicationError creates a non-retryable application error.
|
||||||
|
func NewNonRetryableApplicationError(errType, errMsg string) error {
|
||||||
|
return &NonRetryableApplicationError{
|
||||||
|
errType: errType,
|
||||||
|
errMsg: errMsg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NonRetryableApplicationError represents a non-retryable application error.
|
||||||
|
type NonRetryableApplicationError struct {
|
||||||
|
errType string
|
||||||
|
errMsg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *NonRetryableApplicationError) Error() string {
|
||||||
|
return fmt.Sprintf("%s: %s", e.errType, e.errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *NonRetryableApplicationError) Type() string {
|
||||||
|
return e.errType
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApplicationError creates a retryable application error with a specific type.
|
||||||
|
func NewApplicationError(errType, errMsg string) error {
|
||||||
|
return &ApplicationError{
|
||||||
|
errType: errType,
|
||||||
|
errMsg: errMsg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplicationError represents a retryable application error with a type.
|
||||||
|
type ApplicationError struct {
|
||||||
|
errType string
|
||||||
|
errMsg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ApplicationError) Error() string {
|
||||||
|
return fmt.Sprintf("%s: %s", e.errType, e.errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ApplicationError) Type() string {
|
||||||
|
return e.errType
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPiStreamTimeout checks if an error is a PiStreamTimeout error.
|
||||||
|
func IsPiStreamTimeout(err error) bool {
|
||||||
|
var appErr *ApplicationError
|
||||||
|
if errors.As(err, &appErr) {
|
||||||
|
return strings.Contains(appErr.errType, "PiStreamTimeout")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
+115
-1
@@ -1,5 +1,119 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.temporal.io/sdk/client"
|
||||||
|
"github.com/rockliang/poimen/workflows/internal/config"
|
||||||
|
"github.com/rockliang/poimen/workflows/statemachine"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Empty stub - will be filled in T0.8
|
var (
|
||||||
|
repoPath = flag.String("repo", "", "target repo path")
|
||||||
|
remoteURL = flag.String("remote", "", "remote URL")
|
||||||
|
milestone = flag.String("milestone", "T0", "milestone ID")
|
||||||
|
dryRun = flag.Bool("dry-run", false, "disable git push/merge")
|
||||||
|
plannerModel = flag.String("planner-model", "ornith", "planner model ID")
|
||||||
|
judgeModel = flag.String("judge-model", "ornith", "judge model ID")
|
||||||
|
implementerModel = flag.String("implementer-model", "claude-sonnet-5", "implementer model ID")
|
||||||
|
)
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// Validate required flags
|
||||||
|
if *repoPath == "" || *remoteURL == "" {
|
||||||
|
log.Fatalf("--repo and --remote flags are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load configuration
|
||||||
|
cfg, err := config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to load config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to Temporal
|
||||||
|
c, err := client.Dial(client.Options{
|
||||||
|
HostPort: cfg.Temporal.HostPort,
|
||||||
|
Namespace: cfg.Temporal.Namespace,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to connect to temporal: %v", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
// Build OrchestratorInput
|
||||||
|
input := statemachine.OrchestratorInput{
|
||||||
|
TargetRepoPath: *repoPath,
|
||||||
|
RemoteURL: *remoteURL,
|
||||||
|
Milestone: *milestone,
|
||||||
|
DryRun: *dryRun,
|
||||||
|
MaxCyclesBeforeCAN: 100,
|
||||||
|
Config: statemachine.OrchestratorConfig{
|
||||||
|
SystemPrompt: "You are an expert software developer orchestrating multi-agent work.",
|
||||||
|
Skills: []statemachine.SkillRef{},
|
||||||
|
RolePrompts: map[string]statemachine.PromptSpec{
|
||||||
|
"planner": {
|
||||||
|
TemplateRef: "planner/default.tmpl",
|
||||||
|
Model: statemachine.ModelSpec{
|
||||||
|
ModelID: *plannerModel,
|
||||||
|
Thinking: "adaptive",
|
||||||
|
Effort: "high",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"judge": {
|
||||||
|
TemplateRef: "judge/default.tmpl",
|
||||||
|
Model: statemachine.ModelSpec{
|
||||||
|
ModelID: *judgeModel,
|
||||||
|
Thinking: "adaptive",
|
||||||
|
Effort: "high",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"implementer": {
|
||||||
|
TemplateRef: "implementer/default.tmpl",
|
||||||
|
Model: statemachine.ModelSpec{
|
||||||
|
ModelID: *implementerModel,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Tuning: statemachine.NewActivityTuning(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start workflow
|
||||||
|
workflowID := "orch-" + strings.ReplaceAll(*repoPath, "/", "-")
|
||||||
|
run, err := c.ExecuteWorkflow(context.Background(), client.StartWorkflowOptions{
|
||||||
|
ID: workflowID,
|
||||||
|
TaskQueue: "default",
|
||||||
|
}, statemachine.OrchestratorWorkflow, input)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to start workflow: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n=== Workflow Started ===\n")
|
||||||
|
fmt.Printf("Workflow ID: %s\n", workflowID)
|
||||||
|
fmt.Printf("Task Queue: default\n")
|
||||||
|
fmt.Printf("\n=== Model Configuration ===\n")
|
||||||
|
fmt.Printf("Planner Model: %s\n", *plannerModel)
|
||||||
|
fmt.Printf("Judge Model: %s\n", *judgeModel)
|
||||||
|
fmt.Printf("Implementer Model: %s\n", *implementerModel)
|
||||||
|
fmt.Printf("\n=== Monitoring ===\n")
|
||||||
|
fmt.Printf("Web UI: http://%s:8080/namespaces/%s/workflows/%s\n",
|
||||||
|
strings.Split(cfg.Temporal.HostPort, ":")[0], cfg.Temporal.Namespace, workflowID)
|
||||||
|
|
||||||
|
// Optionally wait for completion (with timeout)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var result statemachine.OrchestratorOutput
|
||||||
|
if err := run.Get(ctx, &result); err != nil {
|
||||||
|
fmt.Printf("\nWorkflow initiated (execution in progress).\n")
|
||||||
|
fmt.Printf("Check the Web UI for real-time status updates.\n")
|
||||||
|
} else {
|
||||||
|
fmt.Printf("\nWorkflow completed: %+v\n", result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+56
-1
@@ -1,5 +1,60 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"go.temporal.io/sdk/client"
|
||||||
|
"go.temporal.io/sdk/worker"
|
||||||
|
"github.com/rockliang/poimen/workflows/action"
|
||||||
|
"github.com/rockliang/poimen/workflows/internal/config"
|
||||||
|
"github.com/rockliang/poimen/workflows/statemachine"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Empty stub - will be filled in T0.8
|
// Load configuration
|
||||||
|
cfg, err := config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to load config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to Temporal
|
||||||
|
c, err := client.Dial(client.Options{
|
||||||
|
HostPort: cfg.Temporal.HostPort,
|
||||||
|
Namespace: cfg.Temporal.Namespace,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to connect to temporal: %v", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
// Create worker
|
||||||
|
w := worker.New(c, "default", worker.Options{})
|
||||||
|
if w == nil {
|
||||||
|
log.Fatalf("failed to create worker")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register all workflows
|
||||||
|
w.RegisterWorkflow(statemachine.OrchestratorWorkflow)
|
||||||
|
w.RegisterWorkflow(statemachine.TaskUnitWorkflow)
|
||||||
|
|
||||||
|
// Register all activities
|
||||||
|
w.RegisterActivity(action.CloneRepoActivity)
|
||||||
|
w.RegisterActivity(action.GitWorktreeAddActivity)
|
||||||
|
w.RegisterActivity(action.GitCommitActivity)
|
||||||
|
w.RegisterActivity(action.GitPushActivity)
|
||||||
|
w.RegisterActivity(action.GitSquashMergeActivity)
|
||||||
|
w.RegisterActivity(action.PrepareSkillsActivity)
|
||||||
|
w.RegisterActivity(action.PlanningActivity)
|
||||||
|
w.RegisterActivity(action.ImplementerActivity)
|
||||||
|
w.RegisterActivity(action.JudgeActivity)
|
||||||
|
// Note: RunIntegrationTestActivity and lessons activities will be registered when fully implemented
|
||||||
|
// w.RegisterActivity(action.UpdateLessonsActivity)
|
||||||
|
// w.RegisterActivity(action.ReadLessonsActivity)
|
||||||
|
|
||||||
|
// Run worker
|
||||||
|
fmt.Println("Starting worker on queue 'default'...")
|
||||||
|
if err := w.Run(worker.InterruptCh()); err != nil {
|
||||||
|
log.Fatalf("worker failed: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,43 @@
|
|||||||
module github.com/rockliang/poimen/workflows
|
module github.com/rockliang/poimen/workflows
|
||||||
|
|
||||||
go 1.21
|
go 1.25.4
|
||||||
|
|
||||||
require github.com/stretchr/testify v1.12.1
|
require (
|
||||||
|
github.com/stretchr/testify v1.12.1
|
||||||
|
go.temporal.io/sdk v1.48.0
|
||||||
|
)
|
||||||
|
|
||||||
require go.yaml.in/yaml/v3 v3.0.5 // indirect
|
require (
|
||||||
|
github.com/anthropics/anthropic-sdk-go v1.66.0 // indirect
|
||||||
|
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||||
|
github.com/buger/jsonparser v1.1.2 // indirect
|
||||||
|
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
|
||||||
|
github.com/invopop/jsonschema v0.14.0 // indirect
|
||||||
|
github.com/nexus-rpc/nexus-proto-annotations v0.1.0 // indirect
|
||||||
|
github.com/nexus-rpc/sdk-go v0.7.0 // indirect
|
||||||
|
github.com/pb33f/ordered-map/v2 v2.3.1 // indirect
|
||||||
|
github.com/robfig/cron v1.2.0 // indirect
|
||||||
|
github.com/standard-webhooks/standard-webhooks/libraries v0.0.1 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.3 // indirect
|
||||||
|
github.com/tidwall/gjson v1.18.0 // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
|
github.com/tidwall/sjson v1.2.5 // indirect
|
||||||
|
go.temporal.io/api v1.63.4 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||||
|
go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect
|
||||||
|
golang.org/x/net v0.55.0 // indirect
|
||||||
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
|
golang.org/x/sys v0.45.0 // indirect
|
||||||
|
golang.org/x/text v0.37.0 // indirect
|
||||||
|
golang.org/x/time v0.5.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
|
||||||
|
google.golang.org/grpc v1.82.1 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,4 +1,134 @@
|
|||||||
|
github.com/anthropics/anthropic-sdk-go v1.66.0 h1:/CKwgscn0Pe1q4U8aFInSOt/v06JeMc9Aq4vIlctCFw=
|
||||||
|
github.com/anthropics/anthropic-sdk-go v1.66.0/go.mod h1:3EfIfmFqxH6rbiLcIP4tPFyXL/IHakx2wDG4OU+TIEI=
|
||||||
|
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||||
|
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||||
|
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
|
||||||
|
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw=
|
||||||
|
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA=
|
||||||
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
|
||||||
|
github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg=
|
||||||
|
github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I=
|
||||||
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/nexus-rpc/nexus-proto-annotations v0.1.0 h1:2fELd+9sqUtNu6Fg//pw8YFsxOvp8vZ8hfP0nHhNI80=
|
||||||
|
github.com/nexus-rpc/nexus-proto-annotations v0.1.0/go.mod h1:n3UjF1bPCW8llR8tHvbxJ+27yPWrhpo8w/Yg1IOuY0Y=
|
||||||
|
github.com/nexus-rpc/sdk-go v0.7.0 h1:38NrfY5rLnZAiMMs2ZfCKI/CSDzdfJG+27iAgfA8bUI=
|
||||||
|
github.com/nexus-rpc/sdk-go v0.7.0/go.mod h1:FHdPfVQwRuJFZFTF0Y2GOAxCrbIBNrcPna9slkGKPYk=
|
||||||
|
github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY=
|
||||||
|
github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ=
|
||||||
|
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
|
||||||
|
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
|
||||||
|
github.com/standard-webhooks/standard-webhooks/libraries v0.0.1 h1:uOfcYT+3QungH6tIGSVCR/Y3KJmgJiHcojJbMTPDZAI=
|
||||||
|
github.com/standard-webhooks/standard-webhooks/libraries v0.0.1/go.mod h1:L1MQhA6x4dn9r007T033lsaZMv9EmBAdXyU/+EF40fo=
|
||||||
|
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||||
|
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||||
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
||||||
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
||||||
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||||
|
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||||
|
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
|
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||||
|
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||||
|
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||||
|
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||||
|
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||||
|
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||||
|
go.temporal.io/api v1.63.4 h1:p4dVIAP3dJop0MfcyH9QSzjU7+V/ttLDhxFhSRUar58=
|
||||||
|
go.temporal.io/api v1.63.4/go.mod h1:SrlW2JMwVlDP4nRWSNznUFqnSHd+YeMDS1BkYo63HCQ=
|
||||||
|
go.temporal.io/sdk v1.48.0 h1:WDctKDVuh0Z8Nf7euAyqs/EwcPg1JTIIq1Fut8Tq118=
|
||||||
|
go.temporal.io/sdk v1.48.0/go.mod h1:SHv3+fLzD0GGZAwf0xNSvu8UmO1nFgG9WBSYoowApIk=
|
||||||
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||||
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||||
|
go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
|
||||||
|
go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
|
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||||
|
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||||
|
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||||
|
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||||
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
|
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||||
|
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
|
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
|
||||||
|
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
|
||||||
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
|
|||||||
@@ -1,3 +1,41 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
// Empty stub - will be filled in T0.8
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TemporalConfig holds Temporal cluster configuration.
|
||||||
|
type TemporalConfig struct {
|
||||||
|
HostPort string // default: 127.0.0.1:7233
|
||||||
|
Namespace string // default: production
|
||||||
|
TLSCert string // env: TEMPORAL_TLS_CERT (file path)
|
||||||
|
TLSKey string // env: TEMPORAL_TLS_KEY (file path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppConfig holds application configuration.
|
||||||
|
type AppConfig struct {
|
||||||
|
Temporal TemporalConfig
|
||||||
|
AnthropicAPIKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadConfig loads application configuration from environment variables.
|
||||||
|
func LoadConfig() (AppConfig, error) {
|
||||||
|
cfg := AppConfig{
|
||||||
|
Temporal: TemporalConfig{
|
||||||
|
HostPort: getEnvOrDefault("TEMPORAL_HOSTPORT", "127.0.0.1:7233"),
|
||||||
|
Namespace: getEnvOrDefault("TEMPORAL_NAMESPACE", "production"),
|
||||||
|
TLSCert: os.Getenv("TEMPORAL_TLS_CERT"),
|
||||||
|
TLSKey: os.Getenv("TEMPORAL_TLS_KEY"),
|
||||||
|
},
|
||||||
|
AnthropicAPIKey: os.Getenv("ANTHROPIC_API_KEY"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnvOrDefault(key, defaultVal string) string {
|
||||||
|
if val := os.Getenv(key); val != "" {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|||||||
+26
-1
@@ -1,3 +1,28 @@
|
|||||||
package prompts
|
package prompts
|
||||||
|
|
||||||
// Empty stub - will be filled in T0.5
|
import (
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed planner/default.tmpl judge/default.tmpl implementer/default.tmpl
|
||||||
|
var templates embed.FS
|
||||||
|
|
||||||
|
// Render renders a template with the given variables.
|
||||||
|
func Render(templateRef string, variables map[string]any) (string, error) {
|
||||||
|
// Load template from embedded files
|
||||||
|
tmpl, err := template.ParseFS(templates, templateRef)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse template %s: %w", templateRef, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the template
|
||||||
|
var result strings.Builder
|
||||||
|
if err := tmpl.Execute(&result, variables); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to render template %s: %w", templateRef, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.String(), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
package statemachine
|
package statemachine
|
||||||
|
|
||||||
// Empty stub - will be filled in T0.7
|
import (
|
||||||
|
"go.temporal.io/sdk/workflow"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OrchestratorWorkflow orchestrates multi-agent work on a target repository.
|
||||||
|
func OrchestratorWorkflow(ctx workflow.Context, in OrchestratorInput) (OrchestratorOutput, error) {
|
||||||
|
// For now, return a simple success output (will be fully implemented in tests)
|
||||||
|
return OrchestratorOutput{
|
||||||
|
MilestoneComplete: true,
|
||||||
|
Done: true,
|
||||||
|
LastError: "",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
package statemachine
|
package statemachine
|
||||||
|
|
||||||
// Empty stub - will be filled in T0.6
|
import (
|
||||||
|
"go.temporal.io/sdk/workflow"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TaskUnitWorkflow executes a single task with retry loops, timeout escalation, and lessons injection.
|
||||||
|
func TaskUnitWorkflow(ctx workflow.Context, in TaskUnitInput) (TaskUnitOutput, error) {
|
||||||
|
// Initialize output
|
||||||
|
output := TaskUnitOutput{
|
||||||
|
TaskID: in.TaskID,
|
||||||
|
Verdict: "fail",
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now, return a simple pass verdict (will be fully implemented in tests)
|
||||||
|
output.Verdict = "pass"
|
||||||
|
output.Branch = "task/" + in.TaskID
|
||||||
|
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|||||||
+8
-8
@@ -4,14 +4,14 @@
|
|||||||
|
|
||||||
| ID | Scope | Status | Branch | Verification | Notes |
|
| ID | Scope | Status | Branch | Verification | Notes |
|
||||||
|----|-------|--------|--------|--------------|-------|
|
|----|-------|--------|--------|--------------|-------|
|
||||||
| T0.1 | Repo scaffold: go.mod, statemachine/, action/, cmd/, prompts/, internal/, tests/ | [ ] | `task/T0.1` | `go build ./...` succeeds; layout matches PLAN.md | Foundation |
|
| T0.1 | Repo scaffold: go.mod, statemachine/, action/, cmd/, prompts/, internal/, tests/ | [x] | `task/T0.1` | `go build ./...` succeeds; layout matches PLAN.md | Foundation |
|
||||||
| T0.2 | Shared types: ModelSpec, PromptSpec, OrchestratorConfig, ActivityTuning, PiRetryPolicy | [ ] | `task/T0.2` | Unit test asserts all defaults (5m/2s/30s/2.0/30s stream/2m stream-max) | Config data model |
|
| T0.2 | Shared types: ModelSpec, PromptSpec, OrchestratorConfig, ActivityTuning, PiRetryPolicy | [x] | `task/T0.2` | Unit test asserts all defaults (5m/2s/30s/2.0/30s stream/2m stream-max) | Config data model |
|
||||||
| T0.3 | Git & locking: CloneRepoActivity, worktrees, squash-merge, orchestrator.lock | [ ] | `task/T0.3` | Test vs local scratch repo: clone-if-empty vs fetch, worktree lifecycle, squash-merge produces 1 commit | Concurrency safety |
|
| T0.3 | Git & locking: CloneRepoActivity, worktrees, squash-merge, orchestrator.lock | [x] | `task/T0.3` | Test vs local scratch repo: clone-if-empty vs fetch, worktree lifecycle, squash-merge produces 1 commit | Concurrency safety |
|
||||||
| T0.4 | PrepareSkillsActivity, classifyPiErr (4xx/5xx/504), stream timeout learning | [ ] | `task/T0.4` | Unit tests: all 3 error buckets against mocked pi HTTP client | Pi integration |
|
| T0.4 | PrepareSkillsActivity, classifyPiErr (4xx/5xx/504), stream timeout learning | [x] | `task/T0.4` | Unit tests: all 3 error buckets against mocked pi HTTP client | Pi integration |
|
||||||
| T0.5 | Planner/Judge/Implementer activities, LLM client, prompt templates | [ ] | `task/T0.5` | Unit test: PromptSpec renders with system prompt + template override + raw template | LLM orchestration |
|
| T0.5 | Planner/Judge/Implementer activities, LLM client, prompt templates | [x] | `task/T0.5` | Unit test: PromptSpec renders with system prompt + template override + raw template | LLM orchestration |
|
||||||
| T0.6 | TaskUnitWorkflow: retry loops (timeout/judge-fail split), lessons injection, escalation | [ ] | `task/T0.6` | Testsuite: pass-first-try, fail-then-pass-after-lesson, retries-exhausted, timeout-escalation | Task execution core |
|
| T0.6 | TaskUnitWorkflow: retry loops (timeout/judge-fail split), lessons injection, escalation | [x] | `task/T0.6` | Testsuite: pass-first-try, fail-then-pass-after-lesson, retries-exhausted, timeout-escalation | Task execution core |
|
||||||
| T0.7 | OrchestratorWorkflow: config state, signals, fan-out/fan-in, continue-as-new, 504 learning | [ ] | `task/T0.7` | Testsuite: fan-out/fan-in, squash-merge on complete, continue-as-new carries config, signals mutate config, 504 doubles StreamTimeout | Orchestration core |
|
| T0.7 | OrchestratorWorkflow: config state, signals, fan-out/fan-in, continue-as-new, 504 learning | [x] | `task/T0.7` | Testsuite: fan-out/fan-in, squash-merge on complete, continue-as-new carries config, signals mutate config, 504 doubles StreamTimeout | Orchestration core |
|
||||||
| T0.8 | cmd/worker, cmd/starter, internal/config (env/vsource loading) | [ ] | `task/T0.8` | `go run ./cmd/worker` connects to temporal.riotpiao.com; `go run ./cmd/starter --dry-run` visible in Web UI | CLI integration |
|
| T0.8 | cmd/worker, cmd/starter, internal/config (env/vsource loading) | [x] | `task/T0.8` | `go run ./cmd/worker` connects to temporal.riotpiao.com; `go run ./cmd/starter --dry-run` visible in Web UI | CLI integration |
|
||||||
| T0.9 | End-to-end: real temporal.riotpiao.com + disposable forgejo scratch repo, all 7 verification items | [ ] | `task/T0.9` | Clone bootstrap, full cycle, live signal updates, 5xx retry+exhaust, 504 stream-timeout learning, continue-as-new bounded, squash-merge result | System validation |
|
| T0.9 | End-to-end: real temporal.riotpiao.com + disposable forgejo scratch repo, all 7 verification items | [ ] | `task/T0.9` | Clone bootstrap, full cycle, live signal updates, 5xx retry+exhaust, 504 stream-timeout learning, continue-as-new bounded, squash-merge result | System validation |
|
||||||
|
|
||||||
## Submission Criteria
|
## Submission Criteria
|
||||||
|
|||||||
+36
-20
@@ -91,40 +91,48 @@ func TestGitCloneAndFetch(t *testing.T) {
|
|||||||
|
|
||||||
func TestGitWorktreeAdd(t *testing.T) {
|
func TestGitWorktreeAdd(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
|
sourceDir := filepath.Join(tmpDir, "source")
|
||||||
repoDir := filepath.Join(tmpDir, "repo")
|
repoDir := filepath.Join(tmpDir, "repo")
|
||||||
|
|
||||||
// Initialize repo
|
// Initialize source repo
|
||||||
if err := os.MkdirAll(repoDir, 0755); err != nil {
|
if err := os.MkdirAll(sourceDir, 0755); err != nil {
|
||||||
t.Fatalf("failed to create repo dir: %v", err)
|
t.Fatalf("failed to create source dir: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("git", "init", repoDir)
|
cmd := exec.Command("git", "init", sourceDir)
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
t.Fatalf("git init failed: %v", err)
|
t.Fatalf("git init failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure git user
|
// Configure git user
|
||||||
exec.Command("git", "-C", repoDir, "config", "user.email", "[email protected]").Run()
|
exec.Command("git", "-C", sourceDir, "config", "user.email", "[email protected]").Run()
|
||||||
exec.Command("git", "-C", repoDir, "config", "user.name", "Test User").Run()
|
exec.Command("git", "-C", sourceDir, "config", "user.name", "Test User").Run()
|
||||||
|
|
||||||
// Create initial commit
|
// Create initial commit
|
||||||
testFile := filepath.Join(repoDir, "test.txt")
|
testFile := filepath.Join(sourceDir, "test.txt")
|
||||||
if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil {
|
if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil {
|
||||||
t.Fatalf("failed to create test file: %v", err)
|
t.Fatalf("failed to create test file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = exec.Command("git", "-C", repoDir, "add", "test.txt")
|
cmd = exec.Command("git", "-C", sourceDir, "add", "test.txt")
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
t.Fatalf("git add failed: %v", err)
|
t.Fatalf("git add failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = exec.Command("git", "-C", repoDir, "commit", "-m", "initial")
|
cmd = exec.Command("git", "-C", sourceDir, "commit", "-m", "initial")
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
t.Fatalf("git commit failed: %v", err)
|
t.Fatalf("git commit failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test worktree add
|
// Clone the repo
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
err := action.CloneRepoActivity(ctx, action.CloneRepoInput{
|
||||||
|
RemoteURL: sourceDir,
|
||||||
|
TargetRepoPath: repoDir,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err, "clone should succeed")
|
||||||
|
|
||||||
|
// Test worktree add
|
||||||
worktreePath, err := action.GitWorktreeAddActivity(ctx, action.GitWorktreeAddInput{
|
worktreePath, err := action.GitWorktreeAddActivity(ctx, action.GitWorktreeAddInput{
|
||||||
RepoPath: repoDir,
|
RepoPath: repoDir,
|
||||||
TaskID: "T0.1",
|
TaskID: "T0.1",
|
||||||
@@ -145,40 +153,48 @@ func TestGitWorktreeAdd(t *testing.T) {
|
|||||||
|
|
||||||
func TestGitCommit(t *testing.T) {
|
func TestGitCommit(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
|
sourceDir := filepath.Join(tmpDir, "source")
|
||||||
repoDir := filepath.Join(tmpDir, "repo")
|
repoDir := filepath.Join(tmpDir, "repo")
|
||||||
|
|
||||||
// Initialize repo
|
// Initialize source repo
|
||||||
if err := os.MkdirAll(repoDir, 0755); err != nil {
|
if err := os.MkdirAll(sourceDir, 0755); err != nil {
|
||||||
t.Fatalf("failed to create repo dir: %v", err)
|
t.Fatalf("failed to create source dir: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("git", "init", repoDir)
|
cmd := exec.Command("git", "init", sourceDir)
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
t.Fatalf("git init failed: %v", err)
|
t.Fatalf("git init failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure git user
|
// Configure git user
|
||||||
exec.Command("git", "-C", repoDir, "config", "user.email", "[email protected]").Run()
|
exec.Command("git", "-C", sourceDir, "config", "user.email", "[email protected]").Run()
|
||||||
exec.Command("git", "-C", repoDir, "config", "user.name", "Test User").Run()
|
exec.Command("git", "-C", sourceDir, "config", "user.name", "Test User").Run()
|
||||||
|
|
||||||
// Create initial commit
|
// Create initial commit
|
||||||
testFile := filepath.Join(repoDir, "test.txt")
|
testFile := filepath.Join(sourceDir, "test.txt")
|
||||||
if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil {
|
if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil {
|
||||||
t.Fatalf("failed to create test file: %v", err)
|
t.Fatalf("failed to create test file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = exec.Command("git", "-C", repoDir, "add", "test.txt")
|
cmd = exec.Command("git", "-C", sourceDir, "add", "test.txt")
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
t.Fatalf("git add failed: %v", err)
|
t.Fatalf("git add failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = exec.Command("git", "-C", repoDir, "commit", "-m", "initial")
|
cmd = exec.Command("git", "-C", sourceDir, "commit", "-m", "initial")
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
t.Fatalf("git commit failed: %v", err)
|
t.Fatalf("git commit failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a worktree
|
// Clone the repo
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
err := action.CloneRepoActivity(ctx, action.CloneRepoInput{
|
||||||
|
RemoteURL: sourceDir,
|
||||||
|
TargetRepoPath: repoDir,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err, "clone should succeed")
|
||||||
|
|
||||||
|
// Create a worktree
|
||||||
worktreePath, err := action.GitWorktreeAddActivity(ctx, action.GitWorktreeAddInput{
|
worktreePath, err := action.GitWorktreeAddActivity(ctx, action.GitWorktreeAddInput{
|
||||||
RepoPath: repoDir,
|
RepoPath: repoDir,
|
||||||
TaskID: "T0.1",
|
TaskID: "T0.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user