1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
package api
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"os/exec"
"time"
)
const validateTimeout = 20 * time.Second
const validateSystemPrompt = `You are a task instruction reviewer for Claudomator, an AI task runner that executes tasks by running Claude as a subprocess.
Analyze the given task name and instructions for clarity and completeness.
Output ONLY a valid JSON object (no markdown fences, no prose, no explanation):
{
"clarity": "clear" | "warning" | "blocking",
"ready": boolean — true if task can proceed without clarification,
"summary": string — 1-2 sentence assessment,
"questions": [{"text": string, "severity": "blocking" | "minor"}],
"suggestions": [string]
}
clarity definitions:
- "clear": instructions are specific, actionable, and complete
- "warning": minor ambiguities exist but task can reasonably proceed
- "blocking": critical information is missing; task cannot succeed without clarification`
type validateResult struct {
Clarity string `json:"clarity"`
Ready bool `json:"ready"`
Questions []validateQuestion `json:"questions"`
Suggestions []string `json:"suggestions"`
Summary string `json:"summary"`
}
type validateQuestion struct {
Severity string `json:"severity"`
Text string `json:"text"`
}
func (s *Server) validateBinaryPath() string {
if s.validateCmdPath != "" {
return s.validateCmdPath
}
return s.claudeBinaryPath()
}
func (s *Server) handleValidateTask(w http.ResponseWriter, r *http.Request) {
if s.elaborateLimiter != nil && !s.elaborateLimiter.allow(realIP(r)) {
writeJSON(w, http.StatusTooManyRequests, map[string]string{"error": "rate limit exceeded"})
return
}
var input struct {
Name string `json:"name"`
Claude struct {
Instructions string `json:"instructions"`
ProjectDir string `json:"project_dir"`
AllowedTools []string `json:"allowed_tools"`
} `json:"claude"`
}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON: " + err.Error()})
return
}
if input.Name == "" {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "name is required"})
return
}
if input.Claude.Instructions == "" {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "instructions are required"})
return
}
userMsg := fmt.Sprintf("Task name: %s\n\nInstructions:\n%s", input.Name, input.Claude.Instructions)
if input.Claude.ProjectDir != "" {
userMsg += fmt.Sprintf("\n\nWorking directory: %s", input.Claude.ProjectDir)
}
if len(input.Claude.AllowedTools) > 0 {
userMsg += fmt.Sprintf("\n\nAllowed tools: %v", input.Claude.AllowedTools)
}
ctx, cancel := context.WithTimeout(r.Context(), validateTimeout)
defer cancel()
cmd := exec.CommandContext(ctx, s.validateBinaryPath(),
"-p", userMsg,
"--system-prompt", validateSystemPrompt,
"--output-format", "json",
"--model", "haiku",
)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
s.logger.Error("validate: claude subprocess failed", "error", err, "stderr", stderr.String())
writeJSON(w, http.StatusBadGateway, map[string]string{
"error": fmt.Sprintf("validation failed: %v", err),
})
return
}
var wrapper claudeJSONResult
if err := json.Unmarshal(stdout.Bytes(), &wrapper); err != nil {
s.logger.Error("validate: failed to parse claude JSON wrapper", "error", err, "stdout", stdout.String())
writeJSON(w, http.StatusBadGateway, map[string]string{
"error": "validation failed: invalid JSON from claude",
})
return
}
var result validateResult
if err := json.Unmarshal([]byte(extractJSON(wrapper.Result)), &result); err != nil {
s.logger.Error("validate: failed to parse validation result", "error", err, "result", wrapper.Result)
writeJSON(w, http.StatusBadGateway, map[string]string{
"error": "validation failed: claude returned invalid result JSON",
})
return
}
writeJSON(w, http.StatusOK, result)
}
|