Skip to content

Commit bb333cf

Browse files
authored
Merge pull request #1468 from helixml/feature/project_management_skill
Feature/project management skill
2 parents d03f52e + 94c76df commit bb333cf

24 files changed

+1606
-1626
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package project
2+
3+
import (
4+
"github.com/helixml/helix/api/pkg/agent"
5+
"github.com/helixml/helix/api/pkg/store"
6+
"github.com/helixml/helix/api/pkg/util/jsonschema"
7+
)
8+
9+
const helixProjectsSystemPrompt = `You are an expert project manager for Helix projects. Your role is to help users manage their development tasks through spec-driven task management.
10+
11+
## Available Operations
12+
13+
You can perform the following operations on spec tasks:
14+
15+
1. **List Tasks** - View all tasks in the project with optional filtering
16+
2. **Create Tasks** - Create new development tasks from user requirements
17+
3. **Get Task Details** - Retrieve full details of a specific task
18+
4. **Update Tasks** - Modify task properties like status, priority, name, or description
19+
20+
## Task Workflow
21+
22+
Tasks follow a spec-driven development workflow with these statuses:
23+
24+
### Phase 1: Specification Generation
25+
- **backlog** - Initial state, task waiting for spec generation
26+
- **spec_generation** - AI is generating specifications (requirements, design, implementation plan)
27+
- **spec_review** - Human reviewing the generated specifications
28+
- **spec_revision** - Human requested changes to the specs
29+
- **spec_approved** - Specs approved, ready for implementation
30+
31+
### Phase 2: Implementation
32+
- **implementation_queued** - Waiting for implementation agent pickup
33+
- **implementation** - Agent actively coding the task
34+
- **implementation_review** - Code review in progress (PR created)
35+
- **pull_request** - PR opened, awaiting merge
36+
- **done** - Task completed
37+
38+
### Error States
39+
- **spec_failed** - Specification generation failed
40+
- **implementation_failed** - Implementation failed
41+
42+
## Task Types
43+
- **feature** - New functionality
44+
- **bug** - Bug fix
45+
- **refactor** - Code refactoring
46+
47+
## Task Priorities
48+
- **low** - Can be addressed when convenient
49+
- **medium** - Should be addressed in normal workflow
50+
- **high** - Should be prioritized
51+
- **critical** - Requires immediate attention
52+
53+
## Best Practices
54+
55+
1. When creating tasks:
56+
- Use clear, descriptive names
57+
- Provide detailed descriptions with context
58+
- Set appropriate priority based on urgency
59+
- Choose the correct task type
60+
61+
2. When updating tasks:
62+
- Only change status according to the workflow progression
63+
- Update priority if urgency changes
64+
- Keep descriptions updated with relevant context
65+
66+
3. When listing tasks:
67+
- Use filters to narrow down results
68+
- Check status distribution to understand project health
69+
- Review high-priority items first
70+
71+
4. Always provide context when creating or updating tasks to ensure the development agent has enough information to work effectively.`
72+
73+
const helixProjectsSkillDescription = `Manage Helix project tasks through spec-driven development workflow.
74+
75+
This skill allows you to:
76+
- List tasks with optional filtering by status, priority, or type
77+
- Create new development tasks from user requirements
78+
- Get full details of specific tasks
79+
- Update task properties like status, priority, name, or description
80+
81+
Use this tool when users want to manage their development tasks, check project status, or create new work items.`
82+
83+
var helixProjectsSkillParameters = jsonschema.Definition{
84+
Type: jsonschema.Object,
85+
Properties: map[string]jsonschema.Definition{
86+
"prompt": {
87+
Type: jsonschema.String,
88+
Description: "The instruction describing what task management operation to perform. Examples: 'list all high priority tasks', 'create a new feature task for user authentication', 'update task status to done'",
89+
},
90+
},
91+
Required: []string{"prompt"},
92+
}
93+
94+
type HelixProjectsSkill struct{}
95+
96+
func NewHelixProjectsSkill(projectID string, store store.Store) agent.Skill {
97+
skillTools := []agent.Tool{
98+
NewListSpecTasksTool(projectID, store),
99+
NewCreateSpecTaskTool(projectID, store),
100+
NewGetSpecTaskTool(projectID, store),
101+
NewUpdateSpecTaskTool(projectID, store),
102+
}
103+
return agent.Skill{
104+
Name: "HelixProjects",
105+
Description: helixProjectsSkillDescription,
106+
SystemPrompt: helixProjectsSystemPrompt,
107+
Parameters: helixProjectsSkillParameters,
108+
Direct: false, // This is not a direct skill, sub-agent context runner will be used
109+
Tools: skillTools,
110+
}
111+
}
112+
113+
func (s *HelixProjectsSkill) Name() string {
114+
return "HelixProjects"
115+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package project
2+
3+
import (
4+
"fmt"
5+
"time"
6+
)
7+
8+
// SpecTaskSummary is a summary of a spec task
9+
type SpecTaskSummary struct {
10+
ID string `json:"id"`
11+
Name string `json:"name"`
12+
Description string `json:"description"`
13+
Status string `json:"status"`
14+
Priority string `json:"priority"`
15+
BranchName string `json:"branch_name,omitempty"`
16+
PullRequestID string `json:"pull_request_id,omitempty"`
17+
PullRequestURL string `json:"pull_request_url,omitempty"`
18+
StartedAt *time.Time `json:"started_at,omitempty"`
19+
CompletedAt *time.Time `json:"completed_at,omitempty"`
20+
}
21+
22+
func (s *SpecTaskSummary) ToString() string {
23+
result := fmt.Sprintf("ID: %s\nTask: %s\nStatus: %s", s.ID, s.Name, s.Status)
24+
if s.Description != "" {
25+
result += fmt.Sprintf("\nDescription: %s", s.Description)
26+
}
27+
if s.Priority != "" {
28+
result += fmt.Sprintf("\nPriority: %s", s.Priority)
29+
}
30+
if s.BranchName != "" {
31+
result += fmt.Sprintf("\nBranchName: %s", s.BranchName)
32+
}
33+
if s.PullRequestID != "" {
34+
result += fmt.Sprintf("\nPullRequestID: %s", s.PullRequestID)
35+
}
36+
if s.PullRequestURL != "" {
37+
result += fmt.Sprintf("\nPullRequestURL: %s", s.PullRequestURL)
38+
}
39+
if s.StartedAt != nil {
40+
result += fmt.Sprintf("\nStartedAt: %s", s.StartedAt.Format(time.RFC3339))
41+
}
42+
if s.CompletedAt != nil {
43+
result += fmt.Sprintf("\nCompletedAt: %s", s.CompletedAt.Format(time.RFC3339))
44+
}
45+
return result
46+
}
47+
48+
type ListSpecTasksResult struct {
49+
Tasks []SpecTaskSummary `json:"tasks"`
50+
Total int `json:"total"`
51+
}
52+
53+
func (r *ListSpecTasksResult) ToString() string {
54+
var result string
55+
result = fmt.Sprintf("Total Tasks: %d\n\n", r.Total)
56+
for i, task := range r.Tasks {
57+
result += fmt.Sprintf("--- Task %d ---\n%s\n\n", i+1, task.ToString())
58+
}
59+
return result
60+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package project
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"github.com/helixml/helix/api/pkg/agent"
9+
"github.com/helixml/helix/api/pkg/store"
10+
"github.com/helixml/helix/api/pkg/system"
11+
"github.com/helixml/helix/api/pkg/types"
12+
"github.com/helixml/helix/api/pkg/util/jsonschema"
13+
"github.com/rs/zerolog/log"
14+
"github.com/sashabaranov/go-openai"
15+
)
16+
17+
// CreateSpecTaskTool - creates a new spec task
18+
19+
var createSpecTaskParameters = jsonschema.Definition{
20+
Type: jsonschema.Object,
21+
Properties: map[string]jsonschema.Definition{
22+
"name": {
23+
Type: jsonschema.String,
24+
Description: "Short, descriptive name for the task",
25+
},
26+
"description": {
27+
Type: jsonschema.String,
28+
Description: "Detailed description of what needs to be done",
29+
},
30+
"type": {
31+
Type: jsonschema.String,
32+
Description: "Task type: feature, bug, or refactor",
33+
Enum: []string{"feature", "bug", "refactor"},
34+
},
35+
"priority": {
36+
Type: jsonschema.String,
37+
Description: "Task priority: low, medium, high, or critical",
38+
Enum: []string{"low", "medium", "high", "critical"},
39+
},
40+
"original_prompt": {
41+
Type: jsonschema.String,
42+
Description: "The original user request or prompt that led to this task",
43+
},
44+
},
45+
Required: []string{"name", "description"},
46+
}
47+
48+
type CreateSpecTaskTool struct {
49+
store store.Store
50+
projectID string
51+
}
52+
53+
func NewCreateSpecTaskTool(projectID string, store store.Store) *CreateSpecTaskTool {
54+
return &CreateSpecTaskTool{
55+
store: store,
56+
projectID: projectID,
57+
}
58+
}
59+
60+
var _ agent.Tool = &CreateSpecTaskTool{}
61+
62+
func (t *CreateSpecTaskTool) Name() string {
63+
return "CreateSpecTask"
64+
}
65+
66+
func (t *CreateSpecTaskTool) Description() string {
67+
return "Create a new spec-driven task in the project"
68+
}
69+
70+
func (t *CreateSpecTaskTool) String() string {
71+
return "CreateSpecTask"
72+
}
73+
74+
func (t *CreateSpecTaskTool) StatusMessage() string {
75+
return "Creating spec task"
76+
}
77+
78+
func (t *CreateSpecTaskTool) Icon() string {
79+
return "AddIcon"
80+
}
81+
82+
func (t *CreateSpecTaskTool) OpenAI() []openai.Tool {
83+
return []openai.Tool{
84+
{
85+
Type: openai.ToolTypeFunction,
86+
Function: &openai.FunctionDefinition{
87+
Name: "CreateSpecTask",
88+
Description: "Create a new spec-driven task in the project",
89+
Parameters: createSpecTaskParameters,
90+
},
91+
},
92+
}
93+
}
94+
95+
type CreateSpecTaskResult struct {
96+
ID string `json:"id"`
97+
Name string `json:"name"`
98+
Description string `json:"description"`
99+
Status string `json:"status"`
100+
Priority string `json:"priority"`
101+
Type string `json:"type"`
102+
Message string `json:"message"`
103+
}
104+
105+
func (r *CreateSpecTaskResult) ToString() string {
106+
return fmt.Sprintf("ID: %s\nTask: %s\nDescription: %s\nStatus: %s\nPriority: %s\nType: %s\nMessage: %s",
107+
r.ID, r.Name, r.Description, r.Status, r.Priority, r.Type, r.Message)
108+
}
109+
110+
func (t *CreateSpecTaskTool) Execute(ctx context.Context, meta agent.Meta, args map[string]interface{}) (string, error) {
111+
projectID := t.projectID
112+
if projectID == "" {
113+
projectContext, ok := types.GetHelixProjectContext(ctx)
114+
if !ok {
115+
return "", fmt.Errorf("helix project context not found")
116+
}
117+
projectID = projectContext.ProjectID
118+
}
119+
120+
log.Info().
121+
Str("project_id", projectID).
122+
Str("user_id", meta.UserID).
123+
Str("session_id", meta.SessionID).
124+
Interface("args", args).
125+
Msg("Executing CreateSpecTask tool")
126+
127+
name, ok := args["name"].(string)
128+
if !ok || name == "" {
129+
return "", fmt.Errorf("name is required")
130+
}
131+
132+
description, ok := args["description"].(string)
133+
if !ok || description == "" {
134+
return "", fmt.Errorf("description is required")
135+
}
136+
137+
taskType := "feature"
138+
if t, ok := args["type"].(string); ok && t != "" {
139+
taskType = t
140+
}
141+
142+
priority := types.SpecTaskPriorityMedium
143+
if p, ok := args["priority"].(string); ok && p != "" {
144+
priority = types.SpecTaskPriority(p)
145+
}
146+
147+
originalPrompt := ""
148+
if op, ok := args["original_prompt"].(string); ok {
149+
originalPrompt = op
150+
}
151+
152+
task := &types.SpecTask{
153+
ID: system.GenerateSpecTaskID(),
154+
ProjectID: projectID,
155+
Name: name,
156+
Description: description,
157+
Type: taskType,
158+
Priority: priority,
159+
Status: types.TaskStatusBacklog,
160+
OriginalPrompt: originalPrompt,
161+
CreatedBy: meta.UserID,
162+
CreatedAt: time.Now(),
163+
UpdatedAt: time.Now(),
164+
}
165+
166+
err := t.store.CreateSpecTask(ctx, task)
167+
if err != nil {
168+
log.Error().Err(err).Str("project_id", projectID).Msg("Failed to create spec task")
169+
return "", fmt.Errorf("failed to create spec task: %w", err)
170+
}
171+
172+
result := CreateSpecTaskResult{
173+
ID: task.ID,
174+
Name: task.Name,
175+
Description: task.Description,
176+
Status: string(task.Status),
177+
Priority: string(task.Priority),
178+
Type: task.Type,
179+
Message: "Task created successfully",
180+
}
181+
182+
return result.ToString(), nil
183+
}

0 commit comments

Comments
 (0)