- Add intelligent-router.sh hook for automatic agent routing - Add AUTO-TRIGGER-SUMMARY.md documentation - Add FINAL-INTEGRATION-SUMMARY.md documentation - Complete Prometheus integration (6 commands + 4 tools) - Complete Dexto integration (12 commands + 5 tools) - Enhanced Ralph with access to all agents - Fix /clawd command (removed disable-model-invocation) - Update hooks.json to v5 with intelligent routing - 291 total skills now available - All 21 commands with automatic routing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
250 lines
8.5 KiB
JavaScript
250 lines
8.5 KiB
JavaScript
/**
|
|
* ESLint rule to enforce .describe() on all Zod schema methods
|
|
*
|
|
* This rule ensures that all Zod schema field definitions include a .describe() call
|
|
* for better OpenAPI documentation generation.
|
|
*
|
|
* Catches patterns like:
|
|
* - z.string().min(5) ❌ Missing .describe()
|
|
* - z.string().min(5).describe('...') ✅ Correct
|
|
* - z.object({ field: z.string() }) ❌ Field missing .describe()
|
|
* - z.object({ field: z.string().describe('...') }) ✅ Correct
|
|
*/
|
|
export default {
|
|
meta: {
|
|
type: 'problem',
|
|
docs: {
|
|
description: 'Enforce .describe() on Zod schema methods for OpenAPI documentation',
|
|
recommended: true,
|
|
},
|
|
fixable: null,
|
|
schema: [
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
exemptSchemaNames: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
description: 'Schema variable names to exempt from this rule (e.g., test mocks)',
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
},
|
|
],
|
|
messages: {
|
|
missingDescribe:
|
|
'Zod schema field "{{field}}" is missing .describe() call. Add .describe("description here") for OpenAPI documentation.',
|
|
missingDescribeChain:
|
|
'Zod method chain is missing .describe() call. Add .describe("description here") for OpenAPI documentation.',
|
|
},
|
|
},
|
|
|
|
create(context) {
|
|
const options = context.options[0] || {};
|
|
const exemptSchemaNames = new Set(options.exemptSchemaNames || []);
|
|
|
|
// Zod primitive types that should have .describe()
|
|
const zodPrimitiveTypes = new Set([
|
|
'string',
|
|
'number',
|
|
'boolean',
|
|
'date',
|
|
'bigint',
|
|
'null',
|
|
'undefined',
|
|
'any',
|
|
'unknown',
|
|
'never',
|
|
'void',
|
|
'literal',
|
|
'enum',
|
|
'nativeEnum',
|
|
]);
|
|
|
|
// Zod method types that should have .describe()
|
|
const zodMethodTypes = new Set([
|
|
'array',
|
|
'tuple',
|
|
'record',
|
|
'map',
|
|
'set',
|
|
'function',
|
|
'lazy',
|
|
'promise',
|
|
'union',
|
|
'discriminatedUnion',
|
|
'intersection',
|
|
]);
|
|
|
|
/**
|
|
* Check if a node is a call to z.object()
|
|
*/
|
|
function isZodObjectCall(node) {
|
|
return (
|
|
node.type === 'CallExpression' &&
|
|
node.callee.type === 'MemberExpression' &&
|
|
node.callee.object.name === 'z' &&
|
|
node.callee.property.name === 'object'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check if a node is a Zod primitive method call (e.g., z.string())
|
|
*/
|
|
function isZodPrimitiveCall(node) {
|
|
if (node.type !== 'CallExpression') return false;
|
|
if (node.callee.type !== 'MemberExpression') return false;
|
|
if (node.callee.object.name !== 'z') return false;
|
|
return zodPrimitiveTypes.has(node.callee.property.name);
|
|
}
|
|
|
|
/**
|
|
* Check if a node is a Zod method call (e.g., z.array())
|
|
*/
|
|
function isZodMethodCall(node) {
|
|
if (node.type !== 'CallExpression') return false;
|
|
if (node.callee.type !== 'MemberExpression') return false;
|
|
if (node.callee.object.name !== 'z') return false;
|
|
return zodMethodTypes.has(node.callee.property.name);
|
|
}
|
|
|
|
/**
|
|
* Check if a call chain has .describe() anywhere in the chain
|
|
*/
|
|
function hasDescribeInChain(node) {
|
|
let current = node;
|
|
while (current) {
|
|
if (
|
|
current.type === 'CallExpression' &&
|
|
current.callee.type === 'MemberExpression' &&
|
|
current.callee.property.name === 'describe'
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
// Move up the chain
|
|
if (current.type === 'CallExpression') {
|
|
current = current.callee.object;
|
|
} else if (current.type === 'MemberExpression') {
|
|
current = current.object;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if a schema variable name is exempt
|
|
*/
|
|
function isExemptSchema(node) {
|
|
// Find the closest variable declaration
|
|
let current = node;
|
|
let depth = 0;
|
|
const maxDepth = 10; // Prevent infinite loops
|
|
|
|
while (current && depth < maxDepth) {
|
|
if (current.type === 'VariableDeclarator' && current.id.type === 'Identifier') {
|
|
return exemptSchemaNames.has(current.id.name);
|
|
}
|
|
current = current.parent;
|
|
depth++;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Walk down a call/member chain to find the root Zod constructor call
|
|
* For z.string().min(5), returns the z.string() node
|
|
* For z.array(z.string()), returns the z.array() node
|
|
*/
|
|
function findRootZodCall(node) {
|
|
let current = node;
|
|
|
|
while (current) {
|
|
// Check if we found a root Zod call
|
|
if (
|
|
isZodPrimitiveCall(current) ||
|
|
isZodMethodCall(current) ||
|
|
isZodObjectCall(current)
|
|
) {
|
|
return current;
|
|
}
|
|
|
|
// Walk down the chain
|
|
if (current.type === 'CallExpression' && current.callee.type === 'MemberExpression') {
|
|
current = current.callee.object;
|
|
continue;
|
|
}
|
|
|
|
if (current.type === 'MemberExpression') {
|
|
current = current.object;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
// Check z.object({ field: z.string() }) patterns
|
|
ObjectExpression(node) {
|
|
// Check if this is inside a z.object() call
|
|
const parent = node.parent;
|
|
if (parent.type !== 'CallExpression') return;
|
|
if (!isZodObjectCall(parent)) return;
|
|
if (isExemptSchema(parent)) return;
|
|
|
|
// Check each property in the object
|
|
for (const property of node.properties) {
|
|
if (property.type !== 'Property') continue;
|
|
if (!property.value) continue;
|
|
|
|
const fieldName =
|
|
property.key.type === 'Identifier'
|
|
? property.key.name
|
|
: property.key.type === 'Literal'
|
|
? property.key.value
|
|
: '<unknown>';
|
|
|
|
// Check if the property value is a Zod call (walk the chain to find root)
|
|
const value = property.value;
|
|
const rootZodCall = findRootZodCall(value);
|
|
if (rootZodCall && !hasDescribeInChain(value)) {
|
|
context.report({
|
|
node: property,
|
|
messageId: 'missingDescribe',
|
|
data: { field: fieldName },
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
// Check z.string(), z.array(), etc. at the top level of variable declarations
|
|
VariableDeclarator(node) {
|
|
if (!node.init) return;
|
|
if (isExemptSchema(node)) return;
|
|
|
|
// Only check if it's a schema definition (ends with "Schema" or "schema")
|
|
if (node.id.type === 'Identifier') {
|
|
const varName = node.id.name;
|
|
if (!varName.endsWith('Schema') && !varName.endsWith('schema')) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Use the same helper to find root Zod call
|
|
const rootZodCall = findRootZodCall(node.init);
|
|
if (rootZodCall && !hasDescribeInChain(node.init)) {
|
|
context.report({
|
|
node: node.init,
|
|
messageId: 'missingDescribeChain',
|
|
});
|
|
}
|
|
},
|
|
};
|
|
},
|
|
};
|