Plugin API
Forge’s plugin system lets you define custom health checks for your codebase as YAML files. Plugins use ast-grep structural patterns to match code and report findings at configurable severity levels.
Plugins require a Solo tier license or higher.
Discovery
Section titled “Discovery”Place YAML plugin files in .forge/plugins/ inside your repository:
<repo>/.forge/plugins/ no-todo-comments.yaml require-error-handling.yaml asset-counts.yamlForge discovers and loads all *.yaml files in this directory automatically on every forge health run and every forge_health_check MCP call. No registration step is required.
Plugin Schema
Section titled “Plugin Schema”Each plugin file defines one check:
# Unique identifier for this check. Used in severity_overrides in team.yml.# Required. Must be unique across all plugins in the directory.id: no-console-log
# Human-readable name shown in forge health output.# Required.name: "No console.log in production code"
# Optional description shown in detailed output.description: "console.log calls should be replaced with a proper logger before shipping."
# Severity of a finding when the pattern matches.# Required. Values: info | warning | error | criticalseverity: warning
# ast-grep pattern to match. Use $VAR for single-node wildcards,# $$$VAR for multi-node spreads.# Required.pattern: "console.log($$$ARGS)"
# Language to restrict the check to. When omitted, the pattern is# tried against all indexed languages.# Optional. Values: typescript | javascript | rust | python | golanguage: typescript
# Glob patterns for files to include. When omitted, all files of the# target language are checked.# Optional.include: - "src/**" - "apps/**"
# Glob patterns for files to exclude. Takes precedence over include.# Optional.exclude: - "**/*.test.ts" - "**/*.spec.ts" - "**/node_modules/**"Field Reference
Section titled “Field Reference”| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Unique identifier. Used in health_checks.severity_overrides in team.yml. Must match [a-z0-9-_]+. |
name | string | yes | Human-readable name shown in forge health output. |
description | string | no | Additional context shown in detailed output. |
severity | string | yes | Finding severity: info, warning, error, or critical. Maps to P3, P2, P1, P0 respectively in health output. |
pattern | string | yes | ast-grep pattern. See Pattern Syntax below. |
language | string | no | Restrict the check to one language: typescript, javascript, rust, python, or go. |
include | array of strings | no | Gitignore-style glob patterns for files to include. When absent, all files of the target language are checked. |
exclude | array of strings | no | Glob patterns to exclude. Takes precedence over include. |
Severity Mapping
Section titled “Severity Mapping”YAML severity | Health output level | Exit code impact |
|---|---|---|
info | P3 Info | None |
warning | P2 Warning | None |
error | P1 Error | forge health exits 1 |
critical | P0 Critical | forge health exits 1 |
Pattern Syntax
Section titled “Pattern Syntax”Forge uses ast-grep patterns. The key wildcard syntax:
| Wildcard | Matches |
|---|---|
$VAR | Any single AST node |
$$$VAR | Zero or more AST nodes (a spread) |
$_ | Any single node (anonymous wildcard) |
| Literal text | Matches exactly |
TypeScript / JavaScript examples:
# Match any console.log callpattern: "console.log($$$ARGS)"
# Match any async functionpattern: "async function $NAME($$$PARAMS) { $$$BODY }"
# Match object spread in JSX propspattern: "<$COMPONENT {...$PROPS} />"
# Match try without catchpattern: "try { $$$BODY } finally { $$$FINALLY }"Rust examples:
# Match any unwrap() callpattern: "$EXPR.unwrap()"language: rust
# Match functions returning Resultpattern: "fn $NAME($$$) -> Result<$$$, $$$> { $$$BODY }"language: rustPython examples:
# Match bare except clausespattern: "try:\n $$$\nexcept:\n $$$"language: pythonExample Plugins
Section titled “Example Plugins”Detect hardcoded TODO comments
Section titled “Detect hardcoded TODO comments”id: no-todo-prodname: "No TODO comments in production code"description: "TODO comments should be resolved before merging to main."severity: warningpattern: "// TODO: $$$TEXT"language: typescriptinclude: - "src/**"exclude: - "**/*.test.ts"Require error handling on async calls
Section titled “Require error handling on async calls”id: require-await-try-catchname: "Async calls should be wrapped in try/catch"severity: infopattern: "const $VAR = await $CALL"language: typescriptinclude: - "src/**"Flag direct database access outside the data layer
Section titled “Flag direct database access outside the data layer”id: no-direct-db-accessname: "Database access outside data layer"description: "Direct DB calls should only appear in src/data/. Use a repository function instead."severity: errorpattern: "db.query($$$)"language: typescriptinclude: - "src/**"exclude: - "src/data/**"Rust: flag expect() with empty messages
Section titled “Rust: flag expect() with empty messages”id: no-empty-expectname: "expect() calls must include an error message"severity: warningpattern: "$EXPR.expect(\"\")"language: rustOverriding Severity in team.yml
Section titled “Overriding Severity in team.yml”You can adjust a plugin’s severity for your team without editing the plugin file. Add overrides to .forge/team.yml:
health_checks: severity_overrides: no-console-log: P3 # Downgrade from warning to info for this team no-direct-db-access: P0 # Upgrade to critical for this teamThe override applies to both built-in checks and plugin checks. The id in severity_overrides must match the plugin’s id field exactly.
Error Handling
Section titled “Error Handling”If a plugin file has a YAML syntax error, Forge logs a P1 PluginParseError finding for that plugin and continues running other plugins. A malformed plugin never blocks forge health or forge_health_check.
If a plugin’s pattern fails to parse (invalid ast-grep syntax), Forge logs a P1 PluginPatternError finding and skips that plugin.
See Error Codes for the full list of plugin error codes.