.cursor/skills/xfi-create-plugin/SKILL.md
Guide for creating a new X-Fidelity plugin from the template. Use when creating plugins, adding new analysis capabilities, or extending the plugin system.
npx skillsauth add zotoio/x-fidelity xfi-create-pluginInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill guides you through creating a new plugin for X-Fidelity.
Plugin Creation Progress:
- [ ] Step 1: Create plugin directory structure
- [ ] Step 2: Create plugin index.ts
- [ ] Step 3: Create fact(s)
- [ ] Step 4: Create operator(s)
- [ ] Step 5: Add tests for facts and operators
- [ ] Step 6: Create sample rule(s)
- [ ] Step 7: Register plugin in main index
- [ ] Step 8: Run tests to verify
packages/x-fidelity-plugins/src/xfiPlugin{Name}/
├── index.ts # Plugin definition and exports
├── types.ts # Plugin-specific types (optional)
├── facts/
│ ├── {name}Fact.ts # Fact implementation
│ └── {name}Fact.test.ts # Fact tests
├── operators/
│ ├── {name}Operator.ts # Operator implementation
│ └── {name}Operator.test.ts # Operator tests
└── sampleRules/
└── {name}-rule.json # Example rule using this plugin
mkdir -p packages/x-fidelity-plugins/src/xfiPluginMyPlugin/{facts,operators,sampleRules}
File: packages/x-fidelity-plugins/src/xfiPluginMyPlugin/index.ts
import { XFiPlugin, PluginError, PluginContext, ILogger } from '@x-fidelity/types';
import { myFact } from './facts/myFact';
import { myOperator } from './operators/myOperator';
let logger: ILogger;
export const xfiPluginMyPlugin: XFiPlugin = {
name: 'xfiPluginMyPlugin',
version: '1.0.0',
description: 'Description of what this plugin analyzes',
facts: [myFact],
operators: [myOperator],
initialize: async (context: PluginContext): Promise<void> => {
logger = context.logger;
logger.info('MyPlugin initialized', {
version: '1.0.0',
factsCount: 1,
operatorsCount: 1
});
},
onError: (error: Error): PluginError => {
if (logger) {
logger.error('Plugin error:', error);
}
return {
message: error.message,
level: 'error',
severity: 'error',
source: 'xfi-plugin-my-plugin',
details: error.stack
};
},
cleanup: async (): Promise<void> => {
if (logger) {
logger.info('MyPlugin cleanup completed');
}
}
};
export { logger as pluginLogger };
File: packages/x-fidelity-plugins/src/xfiPluginMyPlugin/facts/myFact.ts
import { FactDefn } from '@x-fidelity/types';
import { pluginLogger } from '@x-fidelity/core';
interface MyFactParams {
resultFact?: string;
// Add your parameters here
}
interface MyFactAlmanac {
addRuntimeFact?: (name: string, value: any) => void;
factValue?: (name: string) => Promise<any>;
}
export const myFact: FactDefn = {
name: 'myFact',
description: 'Description of what data this fact collects',
type: 'iterative-function', // 'iterative-function' or 'global-function'
priority: 1,
fn: async (params: unknown, almanac?: unknown): Promise<any> => {
const logger = pluginLogger.createOperationLogger('xfi-plugin-my-plugin', 'myFact');
try {
const factParams = params as MyFactParams;
const factAlmanac = almanac as MyFactAlmanac;
logger.debug('Executing myFact', {
paramsKeys: Object.keys(factParams || {})
});
// Collect your data here
const result = { /* your data */ };
// Store result if requested
if (factParams?.resultFact && factAlmanac?.addRuntimeFact) {
factAlmanac.addRuntimeFact(factParams.resultFact, result);
}
logger.debug('myFact completed', { result });
return result;
} catch (error) {
logger.error('Error in myFact:', error);
return null;
}
}
};
| Type | When It Runs | Use Case |
|------|--------------|----------|
| iterative-function | Per matching file | File-specific analysis |
| global-function | Once per repo | Repository-wide checks |
For iterative facts, get file content from almanac:
// Get current file data
const fileData = await factAlmanac?.factValue?.('fileData');
const { fileName, fileContent, filePath } = fileData || {};
File: packages/x-fidelity-plugins/src/xfiPluginMyPlugin/operators/myOperator.ts
import { OperatorDefn } from '@x-fidelity/types';
import { pluginLogger } from '@x-fidelity/core';
export const myOperator: OperatorDefn = {
name: 'myOperator',
description: 'Description of the comparison this operator performs',
fn: (factValue: any, operatorValue: any): boolean => {
const logger = pluginLogger.createOperationLogger('xfi-plugin-my-plugin', 'myOperator');
try {
logger.debug('Executing myOperator', {
factValueType: typeof factValue,
operatorValueType: typeof operatorValue
});
// Your comparison logic here
const result = factValue === operatorValue;
logger.debug('myOperator completed', { result });
return result;
} catch (error) {
logger.error('Error in myOperator:', error);
return false;
}
}
};
// Threshold comparison
const result = factValue > operatorValue;
// Contains check
const result = Array.isArray(factValue) && factValue.includes(operatorValue);
// Pattern matching
const regex = new RegExp(operatorValue);
const result = regex.test(factValue);
// Version comparison (use semver)
import { satisfies } from 'semver';
const result = satisfies(factValue, operatorValue);
File: packages/x-fidelity-plugins/src/xfiPluginMyPlugin/facts/myFact.test.ts
import { myFact } from './myFact';
describe('myFact', () => {
const mockAlmanac = {
factValue: jest.fn(),
addRuntimeFact: jest.fn()
};
beforeEach(() => {
jest.clearAllMocks();
});
it('should return expected data', async () => {
const result = await myFact.fn({}, mockAlmanac);
expect(result).toBeDefined();
});
it('should store result when resultFact is provided', async () => {
await myFact.fn({ resultFact: 'storedResult' }, mockAlmanac);
expect(mockAlmanac.addRuntimeFact).toHaveBeenCalledWith(
'storedResult',
expect.anything()
);
});
it('should handle errors gracefully', async () => {
const badAlmanac = { factValue: jest.fn().mockRejectedValue(new Error('test')) };
const result = await myFact.fn({}, badAlmanac);
expect(result).toBeNull();
});
});
File: packages/x-fidelity-plugins/src/xfiPluginMyPlugin/operators/myOperator.test.ts
import { myOperator } from './myOperator';
describe('myOperator', () => {
it('should return true when values match', () => {
expect(myOperator.fn('test', 'test')).toBe(true);
});
it('should return false when values differ', () => {
expect(myOperator.fn('test', 'different')).toBe(false);
});
it('should handle null/undefined gracefully', () => {
expect(myOperator.fn(null, 'test')).toBe(false);
expect(myOperator.fn(undefined, 'test')).toBe(false);
});
it('should handle edge cases', () => {
expect(myOperator.fn('', '')).toBe(true);
expect(myOperator.fn(0, 0)).toBe(true);
});
});
File: packages/x-fidelity-plugins/src/xfiPluginMyPlugin/sampleRules/myPlugin-iterative-rule.json
{
"name": "myPlugin-iterative",
"conditions": {
"all": [
{
"fact": "fileData",
"path": "$.fileName",
"operator": "notEqual",
"value": "REPO_GLOBAL_CHECK"
},
{
"fact": "myFact",
"params": {
"resultFact": "myFactResult"
},
"operator": "myOperator",
"value": true
}
]
},
"event": {
"type": "warning",
"params": {
"message": "Issue detected by myPlugin",
"details": {
"fact": "myFactResult"
}
}
}
}
Edit packages/x-fidelity-plugins/src/index.ts:
// Add export
export { xfiPluginMyPlugin } from './xfiPluginMyPlugin';
// Add to availablePlugins registry
export const availablePlugins = {
// ... existing plugins
xfiPluginMyPlugin: () => import('./xfiPluginMyPlugin').then(m => m.xfiPluginMyPlugin),
};
# Build the plugins package
yarn workspace @x-fidelity/plugins build
# Run plugin tests
yarn workspace @x-fidelity/plugins test
# Run all tests
yarn test
The sharedPluginUtils module provides common functionality:
import {
TreeSitterManager,
getLanguageFromFilePath,
getAstForContent
} from '../sharedPluginUtils';
// For AST analysis
const ast = await getAstForContent(fileContent, filePath);
pluginLogger for debugging information| Purpose | Location |
|---------|----------|
| Template plugin | packages/x-fidelity-plugins/src/xfiPluginSimpleExample/ |
| Plugin types | packages/x-fidelity-types/src/plugins.ts |
| Shared utils | packages/x-fidelity-plugins/src/sharedPluginUtils/ |
| Plugin registry | packages/x-fidelity-plugins/src/index.ts |
documentation
Guide for managing X-Fidelity releases using the unified release system. Use when releasing, versioning, troubleshooting release issues, or writing commit messages.
documentation
Guide for executing engineering plans through coordinated subagent work. Use when executing existing plans from knowledge/plans/ directory.
development
Guide for updating X-Fidelity documentation including README and website. Use when updating docs, adding new features to documentation, or ensuring docs stay in sync with code.
tools
Guide for debugging X-Fidelity analysis issues. Use when troubleshooting analysis failures, rule evaluation problems, VSCode extension issues, or unexpected results.