Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,38 @@ owner: backend-team

This allows features to be organized anywhere in your codebase while still being discoverable by the tools.

### Feature Metadata Annotations

You can annotate your code with feature metadata using special comments. These annotations can be placed **anywhere in your codebase** and will be automatically associated with the matching feature:

```typescript
// --feature-flag feature:user-auth, type: experiment, owner: team-a, introduced_on: 2025-01-01

export function experimentalLoginFlow() {
// implementation
}
```

The syntax is: `--feature-<metadata-key> feature:<feature-folder-name>, property: value, ...`

- **`feature:`** must match the feature's folder name exactly
- **`<metadata-key>`** becomes a tab in the web UI (e.g., `feature-flag`, `feature-experiment`, `feature-toggle`)
- Properties are flexible - add any key-value pairs you need

This works in any programming language:

```python
# --feature-flag feature:data-pipeline, type: processing, language: python
```

```rust
// --feature-flag feature:api-service, type: core, status: stable
```

```bash
# --feature-flag feature:deployment, environment: production, region: us-west-2
```

> You can find more sophisticated examples in the [examples](./examples) folder.

## License
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// --feature-flag feature:feature-1, type: backend, service: api, version: 2.0

export async function fetchData(endpoint: string): Promise<any> {
const response = await fetch(endpoint);
return response.json();
}

// Another feature flag in the same file
// --feature-flag feature:feature-1, type: frontend, component: DataDisplay

export function DataDisplay({ data }: { data: any }) {
return <div>{JSON.stringify(data)}</div>;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** --feature-flag feature:feature-1, type: experiment, owner: #owner, introduced_on: 2025-12-31 */
export function Foo() {
return <div>foo</div>
return 'foo'
}
15 changes: 15 additions & 0 deletions examples/javascript-large/src/libs/features/feature-2/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/** --feature-flag feature:feature-2, type: ui-component, priority: high */

export function TestComponent() {
return {
render: function() {
console.log("Testing feature 2");
}
};
}

// --feature-flag feature:feature-2, type: utility, status: active

function helperFunction() {
return "helper";
}
20 changes: 20 additions & 0 deletions examples/javascript-large/src/libs/shared/api-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// --feature-flag feature:feature-1, type: shared-dependency, location: libs/shared

export class ApiClient {
private baseUrl: string;

constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}

async get(endpoint: string): Promise<any> {
const response = await fetch(`${this.baseUrl}${endpoint}`);
return response.json();
}
}

// --feature-flag feature:feature-2, type: shared-dependency, location: libs/shared

export function createApiClient(baseUrl: string): ApiClient {
return new ApiClient(baseUrl);
}
34 changes: 34 additions & 0 deletions examples/javascript-large/src/scripts/analyzer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// --feature-flag feature:feature-1, type: analysis, language: cpp, version: 1.0.0

#include <iostream>
#include <string>
#include <vector>

class Analyzer {
private:
std::string name;
std::vector<int> data;

public:
Analyzer(const std::string& name) : name(name) {}

void addData(int value) {
data.push_back(value);
}

double calculateAverage() const {
if (data.empty()) {
return 0.0;
}
double sum = 0.0;
for (int value : data) {
sum += value;
}
return sum / data.size();
}

void printReport() const {
std::cout << "Analyzer: " << name << std::endl;
std::cout << "Average: " << calculateAverage() << std::endl;
}
};
13 changes: 13 additions & 0 deletions examples/javascript-large/src/scripts/another-flag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// --feature-flag feature:feature-1, type: backend, service: api, version: 2.0

export async function fetchData(endpoint: string): Promise<any> {
const response = await fetch(endpoint);
return response.json();
}

// Another feature flag in the same file
// --feature-flag feature:feature-1, type: frontend, component: DataDisplay

export function DataDisplay({ data }: { data: any }) {
return <div>{JSON.stringify(data)}</div>;
}
30 changes: 30 additions & 0 deletions examples/javascript-large/src/scripts/deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash
# --feature-flag feature:feature-1, type: deployment, language: bash, environment: production

set -e

FEATURE_NAME="feature-1"
DEPLOY_DIR="/var/www/features"
BACKUP_DIR="/var/backups/features"

echo "Starting deployment of $FEATURE_NAME..."

# Create backup directory if it doesn't exist
if [ ! -d "$BACKUP_DIR" ]; then
mkdir -p "$BACKUP_DIR"
echo "Created backup directory: $BACKUP_DIR"
fi

# Backup existing deployment
if [ -d "$DEPLOY_DIR/$FEATURE_NAME" ]; then
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
echo "Backing up existing deployment..."
cp -r "$DEPLOY_DIR/$FEATURE_NAME" "$BACKUP_DIR/${FEATURE_NAME}_${TIMESTAMP}"
echo "Backup created: $BACKUP_DIR/${FEATURE_NAME}_${TIMESTAMP}"
fi

# Deploy new version
echo "Deploying new version..."
# Add your deployment commands here

echo "Deployment of $FEATURE_NAME completed successfully!"
13 changes: 13 additions & 0 deletions examples/javascript-large/src/scripts/experiment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// --feature-experiment feature:feature-1, hypothesis: increase-conversions, start_date: 2025-01-01

export class ExperimentTracker {
private experimentId: string;

constructor(id: string) {
this.experimentId = id;
}

track(event: string): void {
console.log(`Tracking ${event} for experiment ${this.experimentId}`);
}
}
4 changes: 4 additions & 0 deletions examples/javascript-large/src/scripts/feature-flag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** --feature-flag feature:feature-1, name: feature-flag1, type: experiment, owner: #owner, introduced_on: 2025-12-31 */
const flag1 = 'feature-flag1'
/** --feature-flag feature:feature-1, name: feature-flag2, type: experiment, owner: #owner, introduced_on: 2025-12-31 */
const flag2 = 'feature-flag2'
38 changes: 38 additions & 0 deletions examples/javascript-large/src/scripts/processor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// --feature-flag feature:feature-1, type: core, language: rust, status: stable

pub struct Processor {
name: String,
capacity: usize,
}

impl Processor {
pub fn new(name: String, capacity: usize) -> Self {
Self { name, capacity }
}

pub fn process(&self, data: &str) -> String {
format!("Processing {} with {}", data, self.name)
}

pub fn get_capacity(&self) -> usize {
self.capacity
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_processor_creation() {
let processor = Processor::new("Test".to_string(), 100);
assert_eq!(processor.get_capacity(), 100);
}

#[test]
fn test_processor_process() {
let processor = Processor::new("Test".to_string(), 100);
let result = processor.process("data");
assert!(result.contains("Test"));
}
}
7 changes: 7 additions & 0 deletions examples/javascript-large/src/scripts/toggle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// --feature-toggle feature:feature-1, enabled: true, rollout_percentage: 50

export function isFeatureEnabled(userId: string): boolean {
// Simple hash-based rollout
const hash = userId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
return (hash % 100) < 50;
}
10 changes: 10 additions & 0 deletions examples/javascript-large/src/scripts/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# --feature-flag feature:feature-1, type: library, language: python

def calculate_sum(a, b):
"""Calculate the sum of two numbers."""
return a + b


def calculate_product(a, b):
"""Calculate the product of two numbers."""
return a * b
7 changes: 7 additions & 0 deletions examples/javascript-large/tests/feature-1.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// --feature-flag feature:feature-1, type: test, test_type: integration, location: tests

describe('Feature 1 Integration Tests', () => {
it('should work correctly', () => {
expect(true).toBe(true);
});
});
1 change: 1 addition & 0 deletions tools/cli/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tools/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ include_dir = "0.7"
notify = "6.0"
openssl = { version = "0.10.73", features = ["vendored"] }
indicatif = "0.17"
walkdir = "2.0"

[dev-dependencies]
tempfile = "3.0"
1 change: 1 addition & 0 deletions tools/cli/src/bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod build;
mod checker;
mod codeowners;
mod coverage_parser;
mod feature_metadata_detector;
mod file_scanner;
mod git_helper;
mod http_server;
Expand Down
Loading