blob: 546a06d835bc1d80d488f86545952b6420ce57e6 [file] [log] [blame]
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +00001// Package experiment provides support for experimental features.
2package experiment
3
4import (
5 "fmt"
6 "io"
Josh Bleecher Snyder6f446fa2025-05-05 14:51:49 -07007 "log/slog"
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +00008 "strings"
9 "sync"
10)
11
12// Experiment represents an experimental feature.
13// Experiments are global.
14type Experiment struct {
15 Name string // The name of the experiment used in -x flag
16 Description string // A short description of what the experiment does
17 Enabled bool // Whether the experiment is enabled
18}
19
20var (
21 mu sync.Mutex
22 experiments = []Experiment{
23 {
24 Name: "list",
25 Description: "List all available experiments and exit",
26 },
27 {
28 Name: "all",
29 Description: "Enable all experiments",
30 },
Josh Bleecher Snyder503b5e32025-05-05 13:30:55 -070031 {
32 Name: "not_done",
33 Description: "Let the model backtrack halfway through a done tool call",
34 },
Josh Bleecher Snydere2518e52025-04-29 11:13:40 -070035 {
36 Name: "llm_review",
37 Description: "Add an LLM step to the codereview tool",
38 },
Josh Bleecher Snyderd7970e62025-05-01 01:56:28 +000039 {
40 Name: "precommit",
41 Description: "Changes title tool to a precommit tool that provides commit message style guidance",
42 },
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +000043 }
44 byName = map[string]*Experiment{}
45)
46
47func Enabled(name string) bool {
48 mu.Lock()
49 defer mu.Unlock()
Josh Bleecher Snyder6f446fa2025-05-05 14:51:49 -070050 e, ok := byName[name]
51 if !ok {
52 slog.Error("unknown experiment", "name", name)
53 return false
54 }
55 return e.Enabled
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +000056}
57
58func init() {
59 for _, e := range experiments {
60 byName[e.Name] = &e
61 }
62}
63
64func (e Experiment) String() string {
65 return fmt.Sprintf("\t%-15s %s\n", e.Name, e.Description)
66}
67
68// Fprint writes a list of all available experiments to w.
69func Fprint(w io.Writer) {
70 mu.Lock()
71 defer mu.Unlock()
72
73 fmt.Fprintln(w, "Available experiments:")
74 for _, e := range experiments {
75 fmt.Fprintln(w, e)
76 }
77}
78
79// Flag is a custom flag type that allows for comma-separated
80// values and can be used multiple times.
81type Flag struct {
82 Value string
83}
84
85// String returns the string representation of the flag value.
86func (f *Flag) String() string {
87 return f.Value
88}
89
90// Set adds a value to the flag.
91func (f *Flag) Set(value string) error {
Josh Bleecher Snyder085f74c2025-05-06 01:58:25 +000092 if f.Value == "" {
93 f.Value = value
94 } else {
95 f.Value = f.Value + "," + value // quadratic, doesn't matter, tiny N
96 }
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +000097 return nil
98}
99
100// Get returns the flag values.
101func (f *Flag) Get() any {
102 return f.Value
103}
104
105// Process handles all flag values, enabling the appropriate experiments.
106func (f *Flag) Process() error {
107 mu.Lock()
108 defer mu.Unlock()
109
110 for name := range strings.SplitSeq(f.Value, ",") {
111 name = strings.TrimSpace(name)
112 if name == "" {
113 continue
114 }
115 e, ok := byName[name]
116 if !ok {
117 return fmt.Errorf("unknown experiment: %q", name)
118 }
119 e.Enabled = true
120 }
121 if byName["all"].Enabled {
Josh Bleecher Snyderd42577f2025-05-05 17:59:00 -0700122 for i := range experiments {
123 e := &experiments[i]
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +0000124 if e.Name == "list" {
125 continue
126 }
127 e.Enabled = true
128 }
129 }
130 return nil
131}