Skip to content

Commit a1cda79

Browse files
committed
feat: add --dump-schema flag for debugging formatting issues
- Added DumpSchema bool field to FormatOptions struct - Added --dump-schema flag to both BindFlags and BindPFlags functions - Modified SchemaFormatter.FormatFiles to dump schema to stderr when flag is set - Schema is dumped in YAML format after loading but before processing data files - Added comprehensive tests to verify the feature works correctly - Output goes to stderr to avoid interfering with normal stdout output
1 parent 61da792 commit a1cda79

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+7918
-4874
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,7 @@ coverage.html
5353
.env
5454
.env.local
5555
test.*
56+
hack/
57+
out.*
58+
clicky-*
59+
test-*

ai/claude.go

Lines changed: 41 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import (
1414

1515
// ClaudeOptions contains configuration for Claude API calls
1616
type ClaudeOptions struct {
17-
Model string
18-
MaxTokens int
19-
Debug bool
20-
StrictMCPConfig bool
21-
OutputFormat string
22-
MaxConcurrent int
17+
Model string
18+
MaxTokens int
19+
Debug bool
20+
StrictMCPConfig bool
21+
OutputFormat string
22+
MaxConcurrent int
2323
}
2424

2525
// ClaudeResponse represents the response from Claude CLI
@@ -49,7 +49,7 @@ func NewClaudeExecutor(options ClaudeOptions) *ClaudeExecutor {
4949
if options.Debug {
5050
tm.SetVerbose(true)
5151
}
52-
52+
5353
return &ClaudeExecutor{
5454
options: options,
5555
taskManager: tm,
@@ -63,40 +63,26 @@ func (ce *ClaudeExecutor) GetTaskManager() *clicky.TaskManager {
6363

6464
// ExecutePrompt executes a single Claude prompt with progress tracking
6565
func (ce *ClaudeExecutor) ExecutePrompt(ctx context.Context, name string, prompt string) (*ClaudeResponse, error) {
66-
task := ce.taskManager.Start(name, clicky.WithFunc(func(ctx flanksourceContext.Context, t *clicky.Task) error {
66+
t := clicky.StartTask[*ClaudeResponse](name, func(ctx flanksourceContext.Context, t *clicky.Task) (*ClaudeResponse, error) {
6767
t.Infof("Starting Claude API call")
6868
t.SetProgress(10, 100)
69-
69+
7070
response, err := ce.executeClaudeCLI(ctx, prompt, t)
7171
if err != nil {
7272
t.Errorf("Claude API failed: %v", err)
73-
return err
73+
return nil, err
7474
}
75-
75+
7676
t.SetProgress(100, 100)
7777
t.Infof("Received response (%d tokens)", response.GetTotalTokens())
78-
78+
7979
// Store response in task context
80-
t.SetStatus(fmt.Sprintf("%s (completed)", name))
81-
return nil
82-
}))
83-
84-
// Wait for this specific task
85-
for task.Status() == clicky.StatusPending || task.Status() == clicky.StatusRunning {
86-
select {
87-
case <-ctx.Done():
88-
task.Cancel()
89-
return nil, ctx.Err()
90-
case <-time.After(100 * time.Millisecond):
91-
}
92-
}
93-
94-
if err := task.Error(); err != nil {
95-
return nil, err
96-
}
97-
80+
t.SetName(fmt.Sprintf("%s (completed)", name))
81+
return response, nil
82+
})
83+
9884
// Execute again to get the actual response (since we can't store it in the task)
99-
return ce.executeClaudeCLI(ctx, prompt, nil)
85+
return t.GetResult()
10086
}
10187

10288
// ExecutePromptBatch executes multiple prompts in parallel with concurrency control
@@ -107,20 +93,19 @@ func (ce *ClaudeExecutor) ExecutePromptBatch(ctx context.Context, prompts map[st
10793
response *ClaudeResponse
10894
err error
10995
}, len(prompts))
110-
96+
11197
// Create tasks for all prompts
11298
for name, prompt := range prompts {
11399
taskName := name
114-
taskPrompt := prompt
115-
116-
ce.taskManager.Start(taskName,
100+
101+
ce.taskManager.Start(taskName,
117102
clicky.WithTimeout(5*time.Minute),
118103
clicky.WithFunc(func(ctx flanksourceContext.Context, t *clicky.Task) error {
119104
t.Infof("Processing prompt")
120105
t.SetProgress(0, 100)
121-
122-
response, err := ce.executeClaudeCLI(t.Context(), taskPrompt, t)
123-
106+
107+
response, err := ce.executeClaudeCLI(t.Context(), prompt, t)
108+
124109
resultsChan <- struct {
125110
name string
126111
response *ClaudeResponse
@@ -130,18 +115,18 @@ func (ce *ClaudeExecutor) ExecutePromptBatch(ctx context.Context, prompts map[st
130115
response: response,
131116
err: err,
132117
}
133-
118+
134119
if err != nil {
135120
t.Errorf("Failed: %v", err)
136121
return err
137122
}
138-
123+
139124
t.SetProgress(100, 100)
140125
t.Infof("Completed (%d tokens)", response.GetTotalTokens())
141126
return nil
142127
}))
143128
}
144-
129+
145130
// Collect results
146131
go func() {
147132
for i := 0; i < len(prompts); i++ {
@@ -156,55 +141,55 @@ func (ce *ClaudeExecutor) ExecutePromptBatch(ctx context.Context, prompts map[st
156141
}
157142
}
158143
}()
159-
144+
160145
// Wait for all tasks to complete
161146
exitCode := ce.taskManager.Wait()
162147
if exitCode != 0 {
163148
return results, fmt.Errorf("some tasks failed (exit code %d)", exitCode)
164149
}
165-
150+
166151
return results, nil
167152
}
168153

169154
// executeClaudeCLI executes the Claude CLI command
170155
func (ce *ClaudeExecutor) executeClaudeCLI(ctx context.Context, prompt string, task *clicky.Task) (*ClaudeResponse, error) {
171156
args := []string{"-p"}
172-
157+
173158
if ce.options.Model != "" {
174159
args = append(args, "--model", ce.options.Model)
175160
}
176-
161+
177162
if ce.options.OutputFormat != "" {
178163
args = append(args, "--output-format", ce.options.OutputFormat)
179164
} else {
180165
args = append(args, "--output-format", "json")
181166
}
182-
167+
183168
if ce.options.StrictMCPConfig {
184169
args = append(args, "--strict-mcp-config")
185170
}
186-
171+
187172
args = append(args, prompt)
188-
173+
189174
if task != nil {
190175
task.Infof("Executing: claude %s", strings.Join(args[:2], " "))
191176
task.SetProgress(30, 100)
192177
}
193-
178+
194179
cmd := exec.CommandContext(ctx, "claude", args...)
195180
output, err := cmd.CombinedOutput()
196-
181+
197182
if task != nil {
198183
task.SetProgress(80, 100)
199184
}
200-
185+
201186
if err != nil {
202187
if ctx.Err() != nil {
203188
return nil, fmt.Errorf("claude CLI cancelled: %w", ctx.Err())
204189
}
205190
return nil, fmt.Errorf("claude CLI failed: %w\nOutput: %s", err, string(output))
206191
}
207-
192+
208193
// Try to parse JSON response
209194
var response ClaudeResponse
210195
if err := json.Unmarshal(output, &response); err != nil {
@@ -214,11 +199,11 @@ func (ce *ClaudeExecutor) executeClaudeCLI(ctx context.Context, prompt string, t
214199
Result: string(output),
215200
}, nil
216201
}
217-
202+
218203
if response.IsError {
219204
return nil, fmt.Errorf("claude returned error: %s", response.Result)
220205
}
221-
206+
222207
if task != nil && ce.options.Debug {
223208
task.Infof("Token usage: input=%d, cache_creation=%d, cache_read=%d, output=%d",
224209
response.Usage.InputTokens,
@@ -227,7 +212,7 @@ func (ce *ClaudeExecutor) executeClaudeCLI(ctx context.Context, prompt string, t
227212
response.Usage.OutputTokens)
228213
task.Infof("Cost: $%.6f USD", response.TotalCostUSD)
229214
}
230-
215+
231216
return &response, nil
232217
}
233218

@@ -241,4 +226,4 @@ func (r *ClaudeResponse) GetTotalTokens() int {
241226
// GetAllTokens returns all tokens including cache reads
242227
func (r *ClaudeResponse) GetAllTokens() int {
243228
return r.GetTotalTokens() + r.Usage.CacheReadInputTokens
244-
}
229+
}

aliases.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package clicky
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/flanksource/clicky/task"
7+
flanksourceContext "github.com/flanksource/commons/context"
8+
)
9+
10+
// Type aliases for backward compatibility
11+
type (
12+
Task = task.Task
13+
TaskManager = task.Manager
14+
TaskGroup = task.Group
15+
TaskStatus = task.Status
16+
TaskOption = task.Option
17+
TaskFunc = task.TaskFunc[any]
18+
TaskResult = task.TaskResult[any]
19+
TypedTask = task.TypedTask[any]
20+
LogEntry = task.LogEntry
21+
RetryConfig = task.RetryConfig
22+
WaitResult = task.WaitResult
23+
Waitable = task.Waitable
24+
TaskManagerOptions = task.ManagerOptions
25+
)
26+
27+
// Status constants
28+
const (
29+
StatusPending = task.StatusPending
30+
StatusRunning = task.StatusRunning
31+
StatusSuccess = task.StatusSuccess
32+
StatusFailed = task.StatusFailed
33+
StatusWarning = task.StatusWarning
34+
StatusCancelled = task.StatusCancelled
35+
)
36+
37+
// Function aliases for backward compatibility
38+
var (
39+
NewTaskManager = task.NewManager
40+
NewTaskManagerWithConcurrency = task.NewManagerWithConcurrency
41+
NewTaskManagerWithOptions = task.NewManagerWithOptions
42+
DefaultRetryConfig = task.DefaultRetryConfig
43+
DefaultTaskManagerOptions = task.DefaultManagerOptions
44+
WithTimeout = task.WithTimeout
45+
WithTaskTimeout = task.WithTaskTimeout
46+
WithDependencies = task.WithDependencies
47+
WithFunc = task.WithFunc
48+
WithModel = task.WithModel
49+
WithPrompt = task.WithPrompt
50+
WithRetryConfig = task.WithRetryConfig
51+
WithPriority = task.WithPriority
52+
BindTaskManagerFlags = task.BindManagerFlags
53+
BindTaskManagerPFlags = task.BindManagerPFlags
54+
)
55+
56+
// StartWithResultTyped creates and starts tracking a new task with generic typed result handling
57+
func StartWithResultTyped[T any](tm *TaskManager, name string, taskFunc task.TaskFunc[T], opts ...TaskOption) *task.Task {
58+
// Wrap the typed function to work with the existing interface{} system
59+
wrappedFunc := func(ctx flanksourceContext.Context, task *task.Task) (interface{}, error) {
60+
result, err := taskFunc(ctx, task)
61+
return result, err
62+
}
63+
return tm.StartWithResult(name, wrappedFunc, opts...)
64+
}
65+
66+
// GetResultTyped returns the stored result with type assertion
67+
func GetResultTyped[T any](t *Task) (T, error) {
68+
result, err := t.GetResult()
69+
var zero T
70+
if err != nil {
71+
return zero, err
72+
}
73+
74+
if result == nil {
75+
return zero, nil
76+
}
77+
78+
if typedResult, ok := result.(T); ok {
79+
return typedResult, nil
80+
}
81+
82+
return zero, fmt.Errorf("result type mismatch: expected %T, got %T", zero, result)
83+
}

0 commit comments

Comments
 (0)