Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# .gitkeep file auto-generated at 2026-06-16T17:58:47.865Z for PR creation at branch issue-87-84fa7bc45fb8 for issue https://github.com/Payel-git-ol/Octra/issues/87
24 changes: 22 additions & 2 deletions orchestrator/internal/service/rules/boss/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,33 @@ func decisionFromPredefinedWorkflow(req *CreateTaskRequest) *DecisionResult {
architecture = "User-defined workflow"
}

// The technical description becomes each worker's TASK.md, so it MUST carry
// the real user request — not the workflow label. Previously this used the
// architecture string ("Custom workflow with Boss and N managers"), so every
// worker was told to implement a meaningless label instead of the actual task
// and produced no code (issue #87). The architecture stays in ArchitectureNotes
// for the Boss validation/context.
technical := strings.TrimSpace(req.Title + "\n" + req.Description)
if technical == "" {
technical = architecture
}

// Honour the user-provided tech stack, but when the custom workflow omits it
// (e.g. the Boss node has no stack configured) fall back to keyword detection
// so workers don't silently default to Go for, say, a Python task — mirroring
// the AI-planning path in thinkOnce.
techStack := append([]string(nil), req.PredefinedTechStack...)
if len(techStack) == 0 {
techStack = detectTechStack(req.Title, req.Description)
}

return &DecisionResult{
TaskType: taskType,
ManagersCount: int32(len(managerRoles)),
ManagerRoles: managerRoles,
ManagerWorkflows: req.PredefinedManagers,
TechnicalDescription: firstNonEmpty(architecture, req.Title+"\n"+req.Description),
TechStack: append([]string(nil), req.PredefinedTechStack...),
TechnicalDescription: technical,
TechStack: techStack,
ArchitectureNotes: architecture,
}
}
Expand Down
65 changes: 62 additions & 3 deletions orchestrator/internal/service/rules/boss/plan_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package boss

import "testing"
import (
"strings"
"testing"
)

func TestDecisionFromPredefinedWorkflow(t *testing.T) {
req := &CreateTaskRequest{
Expand Down Expand Up @@ -34,14 +37,70 @@ func TestDecisionFromPredefinedWorkflow(t *testing.T) {
if got := len(predefinedWorkersFor(decision, 0, "Research Lead")); got != 1 {
t.Fatalf("predefined worker count = %d, want 1", got)
}
if decision.TechnicalDescription != "Custom research workflow" {
t.Fatalf("technical description = %q", decision.TechnicalDescription)
// The technical description must be the real task (what workers implement),
// not the workflow label. The architecture label lives in ArchitectureNotes.
if !strings.Contains(decision.TechnicalDescription, "Research quarterly market data") ||
!strings.Contains(decision.TechnicalDescription, "Find recent sources and produce a short brief") {
t.Fatalf("technical description = %q, want it to contain the real task", decision.TechnicalDescription)
}
if decision.ArchitectureNotes != "Custom research workflow" {
t.Fatalf("architecture notes = %q, want %q", decision.ArchitectureNotes, "Custom research workflow")
}
if len(decision.TechStack) != 1 || decision.TechStack[0] != "web search" {
t.Fatalf("tech stack = %#v, want [web search]", decision.TechStack)
}
}

// TestDecisionFromPredefinedWorkflowCarriesRealTask is the regression test for
// issue #87: a custom workflow built in the canvas (Boss → Managers → Workers)
// must hand workers the actual user request, otherwise they get only the
// "Custom workflow with ... managers" label and produce no code.
func TestDecisionFromPredefinedWorkflowCarriesRealTask(t *testing.T) {
req := &CreateTaskRequest{
Title: "Build a proxy server",
Description: "Write a reverse proxy in Python with load balancing",
// Architecture is the auto-generated label sent by the frontend canvas.
PredefinedArchitecture: "Custom workflow with Architect and 2 managers",
// No tech stack set on the Boss node — must be detected from the task.
PredefinedManagers: []ManagerWorkflow{
{
Role: "Backend",
Description: "Backend manager",
Priority: 1,
Workers: []WorkerWorkflow{
{Role: "Developer", Description: "Developer worker"},
},
},
{
Role: "Coordinator",
Description: "Coordinator manager",
Priority: 2,
Workers: []WorkerWorkflow{
{Role: "Developer", Description: "Developer worker"},
{Role: "Designer", Description: "Designer worker"},
},
},
},
}

decision := decisionFromPredefinedWorkflow(req)

if strings.Contains(decision.TechnicalDescription, "Custom workflow with") {
t.Fatalf("technical description leaked the workflow label: %q", decision.TechnicalDescription)
}
if !strings.Contains(decision.TechnicalDescription, "Build a proxy server") ||
!strings.Contains(decision.TechnicalDescription, "reverse proxy in Python") {
t.Fatalf("technical description lost the real task: %q", decision.TechnicalDescription)
}
if decision.ManagersCount != 2 {
t.Fatalf("managers count = %d, want 2", decision.ManagersCount)
}
// Tech stack must be detected from the request when the workflow omits it.
if len(decision.TechStack) == 0 || decision.TechStack[0] != "python" {
t.Fatalf("tech stack = %#v, want python detected from the task", decision.TechStack)
}
}

func TestFallbackManagerRoleMatchesTaskType(t *testing.T) {
cases := []struct {
taskType string
Expand Down
8 changes: 6 additions & 2 deletions orchestrator/internal/service/rules/worker/tool_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,8 +351,12 @@ func hasRealCode(content string) bool {
}
nonStubLines++
}
// В файле должно быть хотя бы 3 строки реального кода (не комментариев)
return nonStubLines >= 3
// Файл считается реальным кодом, если в нём есть хотя бы одна строка кода,
// а не только пустые строки и комментарии/TODO-заглушки (issue #98). Порог
// «>= 3 строки» был ошибкой: он отбраковывал валидный короткий код вроде
// `package main` + `func main() {}` или однострочного express-сервера, из-за
// чего такой код не считался исходником (см. TestContainsSourceCode, issue #75).
return nonStubLines >= 1
}

// containsSourceCode сообщает, есть ли среди файлов хотя бы один непустой файл с
Expand Down
Loading