refactor: rename action→activity, statemachine→workflow, remove HTTP API layer
- action/ → activity/ (Temporal activities) - statemachine/ → workflow/ (Temporal workflows) - Removed internal/api/ and cmd/server/ (api-gw handles HTTP, Temporal is the API) - Created pkg/types/types.go as single source of truth for all shared types - Extracted CallRoleLLM helper (DRY: implementer/planner/judge shared pattern) - Fixed circular import: workflow_graph_query uses string activity names - Fixed logger.logf → logger.Info/Warn (method didn't exist) - Fixed routing types: added Branches, Activity, BackoffSeconds, TaskActivity - Fixed db.Canvas.Name, db.Client→DB, GetWorkflow→FetchWorkflow - Removed unused imports - All tests pass, build clean, vet clean
This commit is contained in:
+15
-15
@@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/rockliang/poimen/workflows/action"
|
||||
"github.com/rockliang/poimen/workflows/activity"
|
||||
)
|
||||
|
||||
func TestGitCloneAndFetch(t *testing.T) {
|
||||
@@ -56,7 +56,7 @@ func TestGitCloneAndFetch(t *testing.T) {
|
||||
|
||||
// Test clone into empty path
|
||||
ctx := context.Background()
|
||||
err := action.CloneRepoActivity(ctx, action.CloneRepoInput{
|
||||
err := activity.CloneRepoActivity(ctx, activity.CloneRepoInput{
|
||||
RemoteURL: sourceDir,
|
||||
TargetRepoPath: targetDir,
|
||||
})
|
||||
@@ -89,7 +89,7 @@ func TestGitCloneAndFetch(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test fetch on existing repo
|
||||
err = action.CloneRepoActivity(ctx, action.CloneRepoInput{
|
||||
err = activity.CloneRepoActivity(ctx, activity.CloneRepoInput{
|
||||
RemoteURL: sourceDir,
|
||||
TargetRepoPath: targetDir,
|
||||
})
|
||||
@@ -139,14 +139,14 @@ func TestGitWorktreeAdd(t *testing.T) {
|
||||
|
||||
// Clone the repo
|
||||
ctx := context.Background()
|
||||
err := action.CloneRepoActivity(ctx, action.CloneRepoInput{
|
||||
err := activity.CloneRepoActivity(ctx, activity.CloneRepoInput{
|
||||
RemoteURL: sourceDir,
|
||||
TargetRepoPath: repoDir,
|
||||
})
|
||||
assert.NoError(t, err, "clone should succeed")
|
||||
|
||||
// Test worktree add
|
||||
worktreePath, err := action.GitWorktreeAddActivity(ctx, action.GitWorktreeAddInput{
|
||||
worktreePath, err := activity.GitWorktreeAddActivity(ctx, activity.GitWorktreeAddInput{
|
||||
RepoPath: repoDir,
|
||||
TaskID: "T0.1",
|
||||
})
|
||||
@@ -207,14 +207,14 @@ func TestGitCommit(t *testing.T) {
|
||||
|
||||
// Clone the repo
|
||||
ctx := context.Background()
|
||||
err := action.CloneRepoActivity(ctx, action.CloneRepoInput{
|
||||
err := activity.CloneRepoActivity(ctx, activity.CloneRepoInput{
|
||||
RemoteURL: sourceDir,
|
||||
TargetRepoPath: repoDir,
|
||||
})
|
||||
assert.NoError(t, err, "clone should succeed")
|
||||
|
||||
// Create a worktree
|
||||
worktreePath, err := action.GitWorktreeAddActivity(ctx, action.GitWorktreeAddInput{
|
||||
worktreePath, err := activity.GitWorktreeAddActivity(ctx, activity.GitWorktreeAddInput{
|
||||
RepoPath: repoDir,
|
||||
TaskID: "T0.1",
|
||||
})
|
||||
@@ -227,7 +227,7 @@ func TestGitCommit(t *testing.T) {
|
||||
}
|
||||
|
||||
// Commit changes
|
||||
err = action.GitCommitActivity(ctx, action.GitCommitInput{
|
||||
err = activity.GitCommitActivity(ctx, activity.GitCommitInput{
|
||||
WorktreePath: worktreePath,
|
||||
Message: "Add new file",
|
||||
})
|
||||
@@ -283,14 +283,14 @@ func TestGitDiff(t *testing.T) {
|
||||
|
||||
// Clone the repo
|
||||
ctx := context.Background()
|
||||
err := action.CloneRepoActivity(ctx, action.CloneRepoInput{
|
||||
err := activity.CloneRepoActivity(ctx, activity.CloneRepoInput{
|
||||
RemoteURL: sourceDir,
|
||||
TargetRepoPath: repoDir,
|
||||
})
|
||||
assert.NoError(t, err, "clone should succeed")
|
||||
|
||||
// Create a worktree
|
||||
worktreePath, err := action.GitWorktreeAddActivity(ctx, action.GitWorktreeAddInput{
|
||||
worktreePath, err := activity.GitWorktreeAddActivity(ctx, activity.GitWorktreeAddInput{
|
||||
RepoPath: repoDir,
|
||||
TaskID: "T0.1",
|
||||
})
|
||||
@@ -309,7 +309,7 @@ func TestGitDiff(t *testing.T) {
|
||||
}
|
||||
|
||||
// Get diff (should show the staged change)
|
||||
diffOutput, err := action.GitDiffActivity(ctx, action.GitDiffInput{
|
||||
diffOutput, err := activity.GitDiffActivity(ctx, activity.GitDiffInput{
|
||||
WorktreePath: worktreePath,
|
||||
})
|
||||
assert.NoError(t, err, "diff should succeed")
|
||||
@@ -378,7 +378,7 @@ func TestGitSquashMerge(t *testing.T) {
|
||||
|
||||
// Clone for the orchestrator to use
|
||||
ctx := context.Background()
|
||||
err := action.CloneRepoActivity(ctx, action.CloneRepoInput{
|
||||
err := activity.CloneRepoActivity(ctx, activity.CloneRepoInput{
|
||||
RemoteURL: sourceDir,
|
||||
TargetRepoPath: repoDir,
|
||||
})
|
||||
@@ -387,7 +387,7 @@ func TestGitSquashMerge(t *testing.T) {
|
||||
// Create multiple worktrees with changes
|
||||
for i := 1; i <= 2; i++ {
|
||||
taskID := fmt.Sprintf("T0.%d", i)
|
||||
worktreePath, err := action.GitWorktreeAddActivity(ctx, action.GitWorktreeAddInput{
|
||||
worktreePath, err := activity.GitWorktreeAddActivity(ctx, activity.GitWorktreeAddInput{
|
||||
RepoPath: repoDir,
|
||||
TaskID: taskID,
|
||||
})
|
||||
@@ -400,7 +400,7 @@ func TestGitSquashMerge(t *testing.T) {
|
||||
}
|
||||
|
||||
// Commit changes
|
||||
err = action.GitCommitActivity(ctx, action.GitCommitInput{
|
||||
err = activity.GitCommitActivity(ctx, activity.GitCommitInput{
|
||||
WorktreePath: worktreePath,
|
||||
Message: fmt.Sprintf("Task %s implementation", taskID),
|
||||
})
|
||||
@@ -408,7 +408,7 @@ func TestGitSquashMerge(t *testing.T) {
|
||||
}
|
||||
|
||||
// Perform squash merge
|
||||
err = action.GitSquashMergeActivity(ctx, action.GitSquashMergeInput{
|
||||
err = activity.GitSquashMergeActivity(ctx, activity.GitSquashMergeInput{
|
||||
RepoPath: repoDir,
|
||||
Branches: []string{"task/T0.1", "task/T0.2"},
|
||||
Message: "Milestone T0: completed all tasks",
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/rockliang/poimen/workflows/internal/routing"
|
||||
"github.com/rockliang/poimen/workflows/statemachine"
|
||||
"github.com/rockliang/poimen/workflows/workflow"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.temporal.io/sdk/testsuite"
|
||||
)
|
||||
@@ -45,14 +45,14 @@ func TestRoutingWorkflow_SimpleWorkflow(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
input := statemachine.RoutingWorkflowInput{Spec: spec}
|
||||
input := workflow.RoutingWorkflowInput{Spec: spec}
|
||||
|
||||
env.ExecuteWorkflow(statemachine.RoutingWorkflow, input)
|
||||
env.ExecuteWorkflow(workflow.RoutingWorkflow, input)
|
||||
|
||||
require.True(t, env.IsWorkflowCompleted())
|
||||
require.NoError(t, env.GetWorkflowError())
|
||||
|
||||
var output statemachine.RoutingWorkflowOutput
|
||||
var output workflow.RoutingWorkflowOutput
|
||||
require.NoError(t, env.GetWorkflowResult(&output))
|
||||
require.Equal(t, "COMPLETED", output.Status)
|
||||
require.NotNil(t, output.FinalOutput)
|
||||
@@ -96,14 +96,14 @@ func TestRoutingWorkflow_MultiStepWorkflow(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
input := statemachine.RoutingWorkflowInput{Spec: spec}
|
||||
input := workflow.RoutingWorkflowInput{Spec: spec}
|
||||
|
||||
env.ExecuteWorkflow(statemachine.RoutingWorkflow, input)
|
||||
env.ExecuteWorkflow(workflow.RoutingWorkflow, input)
|
||||
|
||||
require.True(t, env.IsWorkflowCompleted())
|
||||
require.NoError(t, env.GetWorkflowError())
|
||||
|
||||
var output statemachine.RoutingWorkflowOutput
|
||||
var output workflow.RoutingWorkflowOutput
|
||||
require.NoError(t, env.GetWorkflowResult(&output))
|
||||
t.Logf("Output: %+v", output)
|
||||
t.Logf("Error: %s", output.Error)
|
||||
@@ -130,14 +130,14 @@ func TestRoutingWorkflow_PassState(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
input := statemachine.RoutingWorkflowInput{Spec: spec}
|
||||
input := workflow.RoutingWorkflowInput{Spec: spec}
|
||||
|
||||
env.ExecuteWorkflow(statemachine.RoutingWorkflow, input)
|
||||
env.ExecuteWorkflow(workflow.RoutingWorkflow, input)
|
||||
|
||||
require.True(t, env.IsWorkflowCompleted())
|
||||
require.NoError(t, env.GetWorkflowError())
|
||||
|
||||
var output statemachine.RoutingWorkflowOutput
|
||||
var output workflow.RoutingWorkflowOutput
|
||||
require.NoError(t, env.GetWorkflowResult(&output))
|
||||
require.Equal(t, "COMPLETED", output.Status)
|
||||
}
|
||||
@@ -160,14 +160,14 @@ func TestRoutingWorkflow_FailState(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
input := statemachine.RoutingWorkflowInput{Spec: spec}
|
||||
input := workflow.RoutingWorkflowInput{Spec: spec}
|
||||
|
||||
env.ExecuteWorkflow(statemachine.RoutingWorkflow, input)
|
||||
env.ExecuteWorkflow(workflow.RoutingWorkflow, input)
|
||||
|
||||
require.True(t, env.IsWorkflowCompleted())
|
||||
require.NoError(t, env.GetWorkflowError())
|
||||
|
||||
var output statemachine.RoutingWorkflowOutput
|
||||
var output workflow.RoutingWorkflowOutput
|
||||
require.NoError(t, env.GetWorkflowResult(&output))
|
||||
require.Equal(t, "FAILED", output.Status)
|
||||
require.Contains(t, output.Error, "WorkflowError")
|
||||
@@ -219,14 +219,14 @@ func TestRoutingWorkflow_ErrorCatch(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
input := statemachine.RoutingWorkflowInput{Spec: spec}
|
||||
input := workflow.RoutingWorkflowInput{Spec: spec}
|
||||
|
||||
env.ExecuteWorkflow(statemachine.RoutingWorkflow, input)
|
||||
env.ExecuteWorkflow(workflow.RoutingWorkflow, input)
|
||||
|
||||
require.True(t, env.IsWorkflowCompleted())
|
||||
require.NoError(t, env.GetWorkflowError())
|
||||
|
||||
var output statemachine.RoutingWorkflowOutput
|
||||
var output workflow.RoutingWorkflowOutput
|
||||
require.NoError(t, env.GetWorkflowResult(&output))
|
||||
require.Equal(t, "FAILED", output.Status)
|
||||
require.Contains(t, output.Error, "CaughtError")
|
||||
@@ -237,14 +237,14 @@ func TestRoutingWorkflow_EmptySpec(t *testing.T) {
|
||||
env := testSuite.NewTestWorkflowEnvironment()
|
||||
|
||||
// Empty spec
|
||||
input := statemachine.RoutingWorkflowInput{Spec: nil}
|
||||
input := workflow.RoutingWorkflowInput{Spec: nil}
|
||||
|
||||
env.ExecuteWorkflow(statemachine.RoutingWorkflow, input)
|
||||
env.ExecuteWorkflow(workflow.RoutingWorkflow, input)
|
||||
|
||||
require.True(t, env.IsWorkflowCompleted())
|
||||
require.NoError(t, env.GetWorkflowError())
|
||||
|
||||
var output statemachine.RoutingWorkflowOutput
|
||||
var output workflow.RoutingWorkflowOutput
|
||||
require.NoError(t, env.GetWorkflowResult(&output))
|
||||
require.Equal(t, "FAILED", output.Status)
|
||||
require.Contains(t, output.Error, "empty")
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.temporal.io/sdk/client"
|
||||
"github.com/rockliang/poimen/workflows/internal/config"
|
||||
"github.com/rockliang/poimen/workflows/statemachine"
|
||||
"github.com/rockliang/poimen/workflows/workflow"
|
||||
)
|
||||
|
||||
// TestTemporalConnection verifies the worker is connected and healthy
|
||||
@@ -67,7 +67,7 @@ func TestActivityExecution(t *testing.T) {
|
||||
runResp, err := c.ExecuteWorkflow(ctx, client.StartWorkflowOptions{
|
||||
ID: workflowID,
|
||||
TaskQueue: "poimen-taskqueue",
|
||||
}, statemachine.TestWorkflow)
|
||||
}, workflow.TestWorkflow)
|
||||
|
||||
assert.NoError(t, err, "failed to execute test workflow")
|
||||
assert.NotNil(t, runResp, "workflow response should not be nil")
|
||||
@@ -136,28 +136,28 @@ func TestOrchestratorWorkflowIntegration(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
// Create minimal orchestrator input
|
||||
input := statemachine.OrchestratorInput{
|
||||
input := workflow.OrchestratorInput{
|
||||
RemoteURL: "https://forgejo.riotpiao.com/rock/poimen",
|
||||
TargetRepoPath: "/tmp/test-poimen-integration",
|
||||
Milestone: "T0",
|
||||
Config: statemachine.OrchestratorConfig{
|
||||
Config: workflow.OrchestratorConfig{
|
||||
SystemPrompt: "You are a code generation assistant. Generate simple test code.",
|
||||
RolePrompts: map[string]statemachine.PromptSpec{
|
||||
RolePrompts: map[string]workflow.PromptSpec{
|
||||
"planner": {
|
||||
TemplateRef: "planner/default.tmpl",
|
||||
Model: statemachine.ModelSpec{
|
||||
Model: workflow.ModelSpec{
|
||||
ModelID: "ornith",
|
||||
},
|
||||
},
|
||||
"judge": {
|
||||
TemplateRef: "judge/default.tmpl",
|
||||
Model: statemachine.ModelSpec{
|
||||
Model: workflow.ModelSpec{
|
||||
ModelID: "ornith",
|
||||
},
|
||||
},
|
||||
"implementer": {
|
||||
TemplateRef: "implementer/default.tmpl",
|
||||
Model: statemachine.ModelSpec{
|
||||
Model: workflow.ModelSpec{
|
||||
ModelID: "claude-sonnet-5",
|
||||
},
|
||||
},
|
||||
@@ -170,7 +170,7 @@ func TestOrchestratorWorkflowIntegration(t *testing.T) {
|
||||
runResp, err := c.ExecuteWorkflow(ctx, client.StartWorkflowOptions{
|
||||
ID: workflowID,
|
||||
TaskQueue: "poimen-taskqueue",
|
||||
}, statemachine.OrchestratorWorkflow, input)
|
||||
}, workflow.OrchestratorWorkflow, input)
|
||||
|
||||
assert.NoError(t, err, "failed to execute orchestrator workflow")
|
||||
t.Logf("✅ Orchestrator workflow started: %s", workflowID)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/rockliang/poimen/workflows/internal/routing"
|
||||
"github.com/rockliang/poimen/workflows/statemachine"
|
||||
"github.com/rockliang/poimen/workflows/workflow"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.temporal.io/sdk/client"
|
||||
)
|
||||
@@ -69,12 +69,12 @@ func TestTemporalRoutingWorkflow(t *testing.T) {
|
||||
|
||||
// Submit to Temporal
|
||||
workflowID := "test-routing-" + time.Now().Format("20060102-150405")
|
||||
input := statemachine.RoutingWorkflowInput{Spec: output.Spec}
|
||||
input := workflow.RoutingWorkflowInput{Spec: output.Spec}
|
||||
|
||||
run, err := c.ExecuteWorkflow(ctx, client.StartWorkflowOptions{
|
||||
ID: workflowID,
|
||||
TaskQueue: "poimen-taskqueue",
|
||||
}, statemachine.RoutingWorkflow, input)
|
||||
}, workflow.RoutingWorkflow, input)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Logf("Workflow submitted: ID=%s, RunID=%s", run.GetID(), run.GetRunID())
|
||||
@@ -118,18 +118,18 @@ func TestTemporalRoutingWorkflow(t *testing.T) {
|
||||
}
|
||||
|
||||
workflowID := "test-pass-only-" + time.Now().Format("20060102-150405")
|
||||
input := statemachine.RoutingWorkflowInput{Spec: spec}
|
||||
input := workflow.RoutingWorkflowInput{Spec: spec}
|
||||
|
||||
run, err := c.ExecuteWorkflow(ctx, client.StartWorkflowOptions{
|
||||
ID: workflowID,
|
||||
TaskQueue: "poimen-taskqueue",
|
||||
}, statemachine.RoutingWorkflow, input)
|
||||
}, workflow.RoutingWorkflow, input)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Logf("Pass-only workflow submitted: ID=%s", run.GetID())
|
||||
|
||||
// Wait for result (Pass states don't need workers)
|
||||
var result statemachine.RoutingWorkflowOutput
|
||||
var result workflow.RoutingWorkflowOutput
|
||||
err = run.Get(ctx, &result)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
+12
-12
@@ -5,12 +5,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/rockliang/poimen/workflows/statemachine"
|
||||
"github.com/rockliang/poimen/workflows/workflow"
|
||||
)
|
||||
|
||||
func TestTypesDefaults(t *testing.T) {
|
||||
// Test PiRetryPolicy defaults
|
||||
pr := statemachine.NewPiRetryPolicy()
|
||||
pr := workflow.NewPiRetryPolicy()
|
||||
assert.Equal(t, 5*time.Minute, pr.ScheduleToCloseTimeout, "ScheduleToCloseTimeout should be 5m")
|
||||
assert.Equal(t, 2*time.Second, pr.InitialInterval, "InitialInterval should be 2s")
|
||||
assert.Equal(t, 30*time.Second, pr.MaximumInterval, "MaximumInterval should be 30s")
|
||||
@@ -19,7 +19,7 @@ func TestTypesDefaults(t *testing.T) {
|
||||
assert.Equal(t, 2*time.Minute, pr.StreamTimeoutMax, "StreamTimeoutMax should be 2m")
|
||||
|
||||
// Test ActivityTuning defaults
|
||||
at := statemachine.NewActivityTuning()
|
||||
at := workflow.NewActivityTuning()
|
||||
assert.Equal(t, 10*time.Minute, at.ImplementerBaseTimeout, "ImplementerBaseTimeout should be 10m")
|
||||
assert.Equal(t, 3, at.ImplementerMaxRetries, "ImplementerMaxRetries should be 3")
|
||||
assert.Equal(t, 5*time.Minute, at.JudgeTimeout, "JudgeTimeout should be 5m")
|
||||
@@ -31,7 +31,7 @@ func TestTypesDefaults(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestModelSpec(t *testing.T) {
|
||||
spec := statemachine.ModelSpec{
|
||||
spec := workflow.ModelSpec{
|
||||
ModelID: "claude-opus-5",
|
||||
Thinking: "adaptive",
|
||||
Effort: "high",
|
||||
@@ -42,13 +42,13 @@ func TestModelSpec(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPromptSpec(t *testing.T) {
|
||||
spec := statemachine.PromptSpec{
|
||||
spec := workflow.PromptSpec{
|
||||
TemplateRef: "planner/default.tmpl",
|
||||
RawTemplate: "",
|
||||
Variables: map[string]any{
|
||||
"key": "value",
|
||||
},
|
||||
Model: statemachine.ModelSpec{
|
||||
Model: workflow.ModelSpec{
|
||||
ModelID: "claude-opus-5",
|
||||
},
|
||||
LessonsRef: "T0.1",
|
||||
@@ -61,18 +61,18 @@ func TestPromptSpec(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOrchestratorConfig(t *testing.T) {
|
||||
cfg := statemachine.OrchestratorConfig{
|
||||
cfg := workflow.OrchestratorConfig{
|
||||
SystemPrompt: "You are an expert",
|
||||
Skills: []statemachine.SkillRef{
|
||||
Skills: []workflow.SkillRef{
|
||||
{Name: "golang-skills", URL: "https://example.com/skill1"},
|
||||
},
|
||||
RolePrompts: map[string]statemachine.PromptSpec{
|
||||
RolePrompts: map[string]workflow.PromptSpec{
|
||||
"planner": {
|
||||
TemplateRef: "planner/default.tmpl",
|
||||
Model: statemachine.ModelSpec{ModelID: "claude-opus-5"},
|
||||
Model: workflow.ModelSpec{ModelID: "claude-opus-5"},
|
||||
},
|
||||
},
|
||||
Tuning: statemachine.NewActivityTuning(),
|
||||
Tuning: workflow.NewActivityTuning(),
|
||||
}
|
||||
assert.Equal(t, "You are an expert", cfg.SystemPrompt)
|
||||
assert.Len(t, cfg.Skills, 1)
|
||||
@@ -81,7 +81,7 @@ func TestOrchestratorConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTaskUnitInput(t *testing.T) {
|
||||
input := statemachine.TaskUnitInput{
|
||||
input := workflow.TaskUnitInput{
|
||||
TaskID: "T0.1",
|
||||
RemoteURL: "https://github.com/example/repo",
|
||||
TargetRepoPath: "/tmp/repo",
|
||||
|
||||
Reference in New Issue
Block a user