Skip to content

Custom Lint Rules

llms.txt lists every page for an agent
Optional: hand this page to your coding agentThe steps work by hand too.
Show the whole prompt
Read https://intentius.io/chant/lexicons/aws/custom-rules/.
Write the project-specific lint rule I describe as a `LintRule` module under src/lint/, in the shape of the lambda-api example the page shows, and register it in the `plugins` array of `chant.config.ts`.
Run `npx chant lint src` and show me the rule firing on a case that breaks it and passing once that case is fixed, then open a pull request with the rule and the config.
Never deploy to a real cloud account or shared environment (with `chant run`, `chant components` or a platform's own CLI), approve (a pull request review, `chant approve` or `chant run approve`), override a policy denial (`chant approve policy-override`), or merge; never cut or publish a release (`chant release cut --yes` or `chant release publish`), push or delete a `ci/*` tag (`chant ci tick` pushes them), or change the value of an existing flag; never touch `.chant/allowed_signers` or `chant/lifecycle`.

Chant’s lint engine runs TypeScript AST visitors. Write project-specific rules that enforce domain conventions.

The lambda-api example includes a full custom rule implementation:

api-timeout.ts
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
import * as ts from "typescript";
const API_FACTORIES = new Set(["LambdaApi", "SecureApi", "HighMemoryApi"]);
/**
* WAW012: API Gateway Lambda Timeout
*
* Detects Lambda API composites with timeout > 29 seconds.
* API Gateway has a maximum synchronous timeout of 29 seconds, so Lambda functions
* behind it should not exceed this limit.
*/
export const apiTimeoutRule: LintRule = {
id: "WAW012",
severity: "error",
category: "correctness",
check(context: LintContext): LintDiagnostic[] {
const { sourceFile } = context;
const diagnostics: LintDiagnostic[] = [];
function visit(node: ts.Node): void {
// Look for `LambdaApi(...)`, `SecureApi(...)`, `HighMemoryApi(...)` calls
if (ts.isCallExpression(node)) {
const expression = node.expression;
let isApiFactory = false;
if (ts.isIdentifier(expression) && API_FACTORIES.has(expression.text)) {
isApiFactory = true;
}
if (isApiFactory && node.arguments && node.arguments.length > 0) {
const props = node.arguments[0];
if (ts.isObjectLiteralExpression(props)) {
for (const prop of props.properties) {
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
if (prop.name.text === "timeout") {
if (ts.isNumericLiteral(prop.initializer)) {
const timeout = Number.parseInt(prop.initializer.text, 10);
if (timeout > 29) {
const { line, character } = sourceFile.getLineAndCharacterOfPosition(
prop.getStart()
);
diagnostics.push({
file: sourceFile.fileName,
line: line + 1,
column: character + 1,
ruleId: "WAW012",
severity: "error",
message: `Lambda function timeout (${timeout}s) exceeds API Gateway's maximum synchronous timeout of 29 seconds. Reduce timeout to 29 or less.`,
});
}
}
}
}
}
}
}
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
return diagnostics;
},
};

The check function receives a LintContext containing the TypeScript sourceFile and returns an array of diagnostics with file, line, column, and message.

Add a chant.config.ts to your project:

chant.config.ts
export default {
lint: {
extends: ["@intentius/chant/lint/presets/strict"],
rules: {
COR004: "off",
},
plugins: ["./lint/api-timeout.ts"],
},
};

The plugins array accepts relative paths. Each plugin module should export a LintRule object.

chant