Skip to content

Commit 138b717

Browse files
committed
feat: update experiment and feature flag handling
1 parent fd3a0dd commit 138b717

File tree

6 files changed

+149
-185
lines changed

6 files changed

+149
-185
lines changed

docs/internal-feature-flags.md

Lines changed: 94 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ export const experimentIds = [
3838
"concurrentFileReads",
3939

4040
// Internal feature flags (prefix with underscore for clarity)
41+
"_nightlyTestBanner",
4142
"_improvedFileReader",
4243
"_asyncToolExecution",
4344
"_enhancedDiffStrategy",
44-
"_experimentalCaching",
4545
] as const
4646
```
4747

@@ -58,10 +58,8 @@ export const EXPERIMENT_IDS = {
5858
CONCURRENT_FILE_READS: "concurrentFileReads",
5959

6060
// Internal flags (use underscore prefix)
61-
_IMPROVED_FILE_READER: "_improvedFileReader",
62-
_ASYNC_TOOL_EXECUTION: "_asyncToolExecution",
63-
_ENHANCED_DIFF_STRATEGY: "_enhancedDiffStrategy",
64-
_EXPERIMENTAL_CACHING: "_experimentalCaching",
61+
_NIGHTLY_TEST_BANNER: "_nightlyTestBanner",
62+
// Add more internal flags as needed
6563
} as const satisfies Record<string, ExperimentId>
6664

6765
interface ExperimentConfig {
@@ -77,30 +75,24 @@ export const experimentConfigsMap: Record<ExperimentKey, ExperimentConfig> = {
7775
CONCURRENT_FILE_READS: { enabled: false },
7876

7977
// Internal flags
80-
_IMPROVED_FILE_READER: {
81-
enabled: false,
82-
internal: true,
83-
nightlyDefault: true,
84-
description: "Internal: Optimized file reading with streaming and caching",
85-
},
86-
_ASYNC_TOOL_EXECUTION: {
78+
_NIGHTLY_TEST_BANNER: {
8779
enabled: false,
88-
internal: true,
80+
internal: false, // Currently visible to users
8981
nightlyDefault: true,
90-
description: "Internal: Parallel tool execution for better performance",
82+
description: "Internal: Shows a test banner in nightly builds",
9183
},
92-
// ... other internal flags
84+
// Add more internal flags as needed
9385
}
9486
```
9587

96-
### 3. Nightly Build Configuration
88+
### 3. Nightly Build Detection
9789

98-
Create a nightly defaults system:
90+
The nightly build detection is implemented as follows:
9991

10092
```typescript
10193
// src/shared/experiments.ts
10294
export function getExperimentDefaults(isNightly: boolean = false): Record<ExperimentId, boolean> {
103-
const defaults: Record<ExperimentId, boolean> = {}
95+
const defaults: Record<ExperimentId, boolean> = {} as Record<ExperimentId, boolean>
10496

10597
Object.entries(experimentConfigsMap).forEach(([key, config]) => {
10698
const experimentId = EXPERIMENT_IDS[key as keyof typeof EXPERIMENT_IDS]
@@ -117,9 +109,9 @@ export function getExperimentDefaults(isNightly: boolean = false): Record<Experi
117109

118110
// Check if running nightly build
119111
export function isNightlyBuild(): boolean {
120-
// Check package name from extension context
121-
const extensionId = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")?.id
122-
return extensionId?.includes("nightly") ?? false
112+
// The nightly build process defines PKG_NAME as "roo-code-nightly" at compile time
113+
// This is the most reliable single indicator for nightly builds
114+
return process.env.PKG_NAME === "roo-code-nightly"
123115
}
124116

125117
// Update experimentDefault to use nightly defaults when appropriate
@@ -128,27 +120,25 @@ export const experimentDefault = getExperimentDefaults(isNightlyBuild())
128120

129121
### 4. Hide Internal Flags from UI
130122

131-
Modify the settings UI to hide internal flags:
123+
To hide internal flags from the settings UI, filter experiments that start with underscore:
132124

133125
```typescript
134126
// webview-ui/src/components/settings/ExperimentalSettings.tsx
135127
<Section>
136128
{Object.entries(experimentConfigsMap)
137-
.filter((config) => {
129+
.filter(([key, config]) => {
138130
// Filter out internal experiments (those starting with underscore)
139-
const experimentId = EXPERIMENT_IDS[config[0] as keyof typeof EXPERIMENT_IDS]
131+
const experimentId = EXPERIMENT_IDS[key as keyof typeof EXPERIMENT_IDS]
140132
return !experimentId.startsWith('_')
141133
})
142-
.map((config) => {
143-
// Render only user-facing experiments
134+
.map(([key, config]) => {
135+
const experimentId = EXPERIMENT_IDS[key as keyof typeof EXPERIMENT_IDS]
144136
return (
145137
<ExperimentalFeature
146-
key={config[0]}
147-
experimentKey={config[0]}
148-
enabled={experiments[EXPERIMENT_IDS[config[0] as keyof typeof EXPERIMENT_IDS]] ?? false}
149-
onChange={(enabled) =>
150-
setExperimentEnabled(EXPERIMENT_IDS[config[0] as keyof typeof EXPERIMENT_IDS], enabled)
151-
}
138+
key={key}
139+
experimentKey={key}
140+
enabled={experiments[experimentId] ?? false}
141+
onChange={(enabled) => setExperimentEnabled(experimentId, enabled)}
152142
/>
153143
)
154144
})}
@@ -159,59 +149,48 @@ Modify the settings UI to hide internal flags:
159149

160150
### 1. Nightly Build Workflow
161151

162-
Update `.github/workflows/nightly-publish.yml`:
152+
The nightly build workflow (`.github/workflows/nightly-publish.yml`) includes:
163153

164154
```yaml
165-
- name: Enable Internal Feature Flags
155+
- name: Log Internal Experiments
166156
run: |
167-
# Set environment variable for nightly build
168-
echo "ROO_CODE_NIGHTLY=true" >> $GITHUB_ENV
169-
170157
# Log enabled experiments
171-
node scripts/log-experiments.js --nightly
158+
node scripts/log-experiments.js
172159
```
173160

174-
### 2. Validation Script (Optional)
161+
### 2. Build Configuration
175162

176-
Create `scripts/validate-internal-flags.js`:
163+
The nightly build process sets the PKG_NAME environment variable in `apps/vscode-nightly/esbuild.mjs`:
177164

178165
```javascript
179-
#!/usr/bin/env node
180-
181-
const { EXPERIMENT_IDS, experimentConfigsMap } = require("../src/shared/experiments")
182-
183-
// Validate internal flags
184-
const internalFlags = Object.entries(experimentConfigsMap)
185-
.filter(([_, config]) => config.internal)
186-
.map(([key, config]) => ({
187-
key,
188-
id: EXPERIMENT_IDS[key],
189-
...config,
190-
}))
191-
192-
console.log(`Found ${internalFlags.length} internal feature flags:`)
193-
internalFlags.forEach(({ id, nightlyDefault }) => {
194-
console.log(` - ${id}: nightlyDefault=${nightlyDefault}`)
195-
})
196-
197-
// Ensure internal flags are prefixed
198-
const invalidFlags = internalFlags.filter(({ id }) => !id.startsWith("_"))
199-
if (invalidFlags.length > 0) {
200-
console.error("❌ Internal flags must start with underscore:")
201-
invalidFlags.forEach(({ id }) => console.error(` - ${id}`))
202-
process.exit(1)
166+
define: {
167+
"process.env.PKG_NAME": '"roo-code-nightly"',
168+
"process.env.PKG_VERSION": `"${overrideJson.version}"`,
169+
"process.env.PKG_OUTPUT_CHANNEL": '"Roo-Code-Nightly"',
170+
// ... other defines
203171
}
204-
205-
console.log("✅ All internal flags are properly configured")
206172
```
207173

208-
### 3. Gradual Rollout Process
174+
### 3. Experiment Logging Script
175+
176+
The `scripts/log-experiments.js` script:
177+
178+
- Checks if running in nightly mode via `process.env.PKG_NAME === "roo-code-nightly"`
179+
- Parses the experiments configuration from the TypeScript source
180+
- Logs which experiments are enabled based on build type
181+
- Separates user-facing and internal experiments in the output
182+
183+
### 4. Gradual Rollout Process
184+
185+
For managing the lifecycle of internal flags, you can extend the configuration:
209186

210187
```typescript
211188
// src/shared/experiments.ts
212-
export interface InternalExperimentConfig extends ExperimentConfig {
213-
internal: boolean
214-
nightlyDefault: boolean
189+
interface ExperimentConfig {
190+
enabled: boolean
191+
internal?: boolean
192+
nightlyDefault?: boolean
193+
description?: string
215194
stableRolloutDate?: string // When to enable in stable
216195
removalDate?: string // When to remove the flag
217196
}
@@ -496,21 +475,14 @@ export class MultiSearchReplaceDiffStrategy {
496475

497476
```typescript
498477
// packages/types/src/experiment.ts
499-
import { z } from "zod"
500-
501478
export const experimentIds = [
502479
"powerSteering",
503480
"concurrentFileReads",
504481
// Add new internal flag
505482
"_enhancedDiffStrategy",
506483
] as const
507484
508-
export const experimentsSchema = z.object({
509-
powerSteering: z.boolean(),
510-
concurrentFileReads: z.boolean(),
511-
// Add schema for new flag
512-
_enhancedDiffStrategy: z.boolean(),
513-
})
485+
export type ExperimentId = (typeof experimentIds)[number]
514486
```
515487

516488
### Step 2: Update Experiments Configuration
@@ -545,25 +517,29 @@ export const experimentConfigsMap: Record<ExperimentKey, ExperimentConfig> = {
545517
// webview-ui/src/components/settings/ExperimentalSettings.tsx
546518
import { experimentConfigsMap, EXPERIMENT_IDS } from "../../../src/shared/experiments"
547519
548-
<Section>
549-
{Object.entries(experimentConfigsMap)
550-
.filter(([key, config]) => {
551-
// Filter out internal experiments
552-
const experimentId = EXPERIMENT_IDS[key as keyof typeof EXPERIMENT_IDS]
553-
return !experimentId.startsWith('_')
554-
})
555-
.map(([key, config]) => {
556-
const experimentId = EXPERIMENT_IDS[key as keyof typeof EXPERIMENT_IDS]
557-
return (
558-
<ExperimentalFeature
559-
key={key}
560-
experimentKey={key}
561-
enabled={experiments[experimentId] ?? false}
562-
onChange={(enabled) => setExperimentEnabled(experimentId, enabled)}
563-
/>
564-
)
565-
})}
566-
</Section>
520+
export function ExperimentalSettings({ experiments, setExperimentEnabled }) {
521+
return (
522+
<Section>
523+
{Object.entries(experimentConfigsMap)
524+
.filter(([key, config]) => {
525+
// Filter out internal experiments
526+
const experimentId = EXPERIMENT_IDS[key as keyof typeof EXPERIMENT_IDS]
527+
return !experimentId.startsWith('_')
528+
})
529+
.map(([key, config]) => {
530+
const experimentId = EXPERIMENT_IDS[key as keyof typeof EXPERIMENT_IDS]
531+
return (
532+
<ExperimentalFeature
533+
key={key}
534+
experimentKey={key}
535+
enabled={experiments[experimentId] ?? false}
536+
onChange={(enabled) => setExperimentEnabled(experimentId, enabled)}
537+
/>
538+
)
539+
})}
540+
</Section>
541+
)
542+
}
567543
```
568544

569545
### Step 4: Use the Internal Flag in Code
@@ -735,12 +711,28 @@ describe("Apply Diff with internal flags", () => {
735711

736712
### Manual Testing in Development
737713

714+
To test internal flags during development:
715+
716+
1. **Temporarily enable in code**:
717+
718+
```typescript
719+
// For testing, temporarily modify the config
720+
experimentConfigsMap._ENHANCED_DIFF_STRATEGY.enabled = true
721+
```
722+
723+
2. **Use environment variable**:
724+
725+
```bash
726+
# Set PKG_NAME to simulate nightly build
727+
PKG_NAME=roo-code-nightly npm run dev
728+
```
729+
730+
3. **Modify the isNightlyBuild function temporarily**:
731+
738732
```typescript
739-
// For testing, temporarily enable the flag in development
740-
// src/extension.ts
741-
if (process.env.NODE_ENV === "development") {
742-
// Override specific internal flags for testing
743-
experimentDefault._enhancedDiffStrategy = true
733+
export function isNightlyBuild(): boolean {
734+
// For testing only - remove before committing
735+
return true
744736
}
745737
```
746738

packages/types/src/experiment.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { Keys, Equals, AssertEqual } from "./type-fu.js"
66
* ExperimentId
77
*/
88

9-
export const experimentIds = ["powerSteering", "concurrentFileReads"] as const
9+
export const experimentIds = ["powerSteering", "concurrentFileReads", "_nightlyTestBanner"] as const
1010

1111
export const experimentIdsSchema = z.enum(experimentIds)
1212

@@ -19,6 +19,7 @@ export type ExperimentId = z.infer<typeof experimentIdsSchema>
1919
export const experimentsSchema = z.object({
2020
powerSteering: z.boolean(),
2121
concurrentFileReads: z.boolean(),
22+
_nightlyTestBanner: z.boolean(),
2223
})
2324

2425
export type Experiments = z.infer<typeof experimentsSchema>

scripts/log-experiments.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const path = require("path")
44
const fs = require("fs")
55

66
// Check if running in nightly mode
7-
const isNightly = process.env.ROO_CODE_NIGHTLY === "true"
7+
const isNightly = process.env.PKG_NAME === "roo-code-nightly"
88

99
// Read the experiments configuration
1010
const experimentsPath = path.join(__dirname, "../src/shared/experiments.ts")

scripts/validate-internal-flags.js

Lines changed: 0 additions & 67 deletions
This file was deleted.

0 commit comments

Comments
 (0)