feat: add /features command and lifecycle visibility

This commit is contained in:
Jerop Kipruto
2026-02-18 11:53:18 -05:00
parent 04f22a51b1
commit 0640faa4c6
11 changed files with 473 additions and 1 deletions
+7
View File
@@ -1126,6 +1126,13 @@ export class Config implements McpContext {
);
}
/**
* Returns the feature gate for querying feature status.
*/
getFeatureGate(): FeatureGate {
return this.featureGate;
}
isInitialized(): boolean {
return this.initialized;
}
+42
View File
@@ -100,6 +100,48 @@ describe('FeatureGate', () => {
expect(gate.enabled('testGA')).toBe(true);
});
it('should return feature info with metadata', () => {
const gate = DefaultFeatureGate.deepCopy();
gate.add({
feat1: [
{
preRelease: FeatureStage.Alpha,
since: '0.1.0',
description: 'Feature 1',
},
],
feat2: [
{
preRelease: FeatureStage.Beta,
since: '0.2.0',
until: '0.3.0',
description: 'Feature 2',
},
],
});
const info = gate.getFeatureInfo();
const feat1 = info.find((f) => f.key === 'feat1');
const feat2 = info.find((f) => f.key === 'feat2');
expect(feat1).toEqual({
key: 'feat1',
enabled: false,
stage: FeatureStage.Alpha,
since: '0.1.0',
until: undefined,
description: 'Feature 1',
});
expect(feat2).toEqual({
key: 'feat2',
enabled: true,
stage: FeatureStage.Beta,
since: '0.2.0',
until: '0.3.0',
description: 'Feature 2',
});
});
it('should respect allAlpha/allBeta toggles', () => {
const gate = DefaultFeatureGate.deepCopy();
gate.add({
+32
View File
@@ -67,6 +67,18 @@ export interface FeatureSpec {
description?: string;
}
/**
* FeatureInfo provides a summary of a feature's current state and metadata.
*/
export interface FeatureInfo {
key: string;
enabled: boolean;
stage: FeatureStage;
since?: string;
until?: string;
description?: string;
}
/**
* FeatureGate provides a read-only interface to query feature status.
*/
@@ -79,6 +91,10 @@ export interface FeatureGate {
* Returns all known feature keys.
*/
knownFeatures(): string[];
/**
* Returns all features with their status and metadata.
*/
getFeatureInfo(): FeatureInfo[];
/**
* Returns a mutable copy of the current gate.
*/
@@ -189,6 +205,22 @@ class FeatureGateImpl implements MutableFeatureGate {
return Array.from(this.specs.keys());
}
getFeatureInfo(): FeatureInfo[] {
return Array.from(this.specs.entries())
.map(([key, specs]) => {
const latestSpec = specs[specs.length - 1];
return {
key,
enabled: this.enabled(key),
stage: latestSpec.preRelease,
since: latestSpec.since,
until: latestSpec.until,
description: latestSpec.description,
};
})
.sort((a, b) => a.key.localeCompare(b.key));
}
deepCopy(): MutableFeatureGate {
const copy = new FeatureGateImpl();
copy.specs = new Map(