blob: 826c56ca1b13814833747cd52b6de078c5eace60 [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"
7 "strings"
8 "sync"
9)
10
11// Experiment represents an experimental feature.
12// Experiments are global.
13type Experiment struct {
14 Name string // The name of the experiment used in -x flag
15 Description string // A short description of what the experiment does
16 Enabled bool // Whether the experiment is enabled
17}
18
19var (
20 mu sync.Mutex
21 experiments = []Experiment{
22 {
23 Name: "list",
24 Description: "List all available experiments and exit",
25 },
26 {
27 Name: "all",
28 Description: "Enable all experiments",
29 },
Josh Bleecher Snyder503b5e32025-05-05 13:30:55 -070030 {
31 Name: "not_done",
32 Description: "Let the model backtrack halfway through a done tool call",
33 },
Josh Bleecher Snydere2518e52025-04-29 11:13:40 -070034 {
35 Name: "llm_review",
36 Description: "Add an LLM step to the codereview tool",
37 },
Josh Bleecher Snyderd7970e62025-05-01 01:56:28 +000038 {
39 Name: "precommit",
40 Description: "Changes title tool to a precommit tool that provides commit message style guidance",
41 },
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +000042 }
43 byName = map[string]*Experiment{}
44)
45
46func Enabled(name string) bool {
47 mu.Lock()
48 defer mu.Unlock()
49 return byName[name].Enabled
50}
51
52func init() {
53 for _, e := range experiments {
54 byName[e.Name] = &e
55 }
56}
57
58func (e Experiment) String() string {
59 return fmt.Sprintf("\t%-15s %s\n", e.Name, e.Description)
60}
61
62// Fprint writes a list of all available experiments to w.
63func Fprint(w io.Writer) {
64 mu.Lock()
65 defer mu.Unlock()
66
67 fmt.Fprintln(w, "Available experiments:")
68 for _, e := range experiments {
69 fmt.Fprintln(w, e)
70 }
71}
72
73// Flag is a custom flag type that allows for comma-separated
74// values and can be used multiple times.
75type Flag struct {
76 Value string
77}
78
79// String returns the string representation of the flag value.
80func (f *Flag) String() string {
81 return f.Value
82}
83
84// Set adds a value to the flag.
85func (f *Flag) Set(value string) error {
86 f.Value = f.Value + "," + value // quadratic, doesn't matter, tiny N
87 return nil
88}
89
90// Get returns the flag values.
91func (f *Flag) Get() any {
92 return f.Value
93}
94
95// Process handles all flag values, enabling the appropriate experiments.
96func (f *Flag) Process() error {
97 mu.Lock()
98 defer mu.Unlock()
99
100 for name := range strings.SplitSeq(f.Value, ",") {
101 name = strings.TrimSpace(name)
102 if name == "" {
103 continue
104 }
105 e, ok := byName[name]
106 if !ok {
107 return fmt.Errorf("unknown experiment: %q", name)
108 }
109 e.Enabled = true
110 }
111 if byName["all"].Enabled {
112 for _, e := range experiments {
113 if e.Name == "list" {
114 continue
115 }
116 e.Enabled = true
117 }
118 }
119 return nil
120}