Skip to content

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.


Place YAML plugin files in .forge/plugins/ inside your repository:

<repo>/.forge/plugins/
no-todo-comments.yaml
require-error-handling.yaml
asset-counts.yaml

Forge 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.


Each plugin file defines one check:

.forge/plugins/no-console-log.yaml
# 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 | critical
severity: 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 | go
language: 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/**"

FieldTypeRequiredDescription
idstringyesUnique identifier. Used in health_checks.severity_overrides in team.yml. Must match [a-z0-9-_]+.
namestringyesHuman-readable name shown in forge health output.
descriptionstringnoAdditional context shown in detailed output.
severitystringyesFinding severity: info, warning, error, or critical. Maps to P3, P2, P1, P0 respectively in health output.
patternstringyesast-grep pattern. See Pattern Syntax below.
languagestringnoRestrict the check to one language: typescript, javascript, rust, python, or go.
includearray of stringsnoGitignore-style glob patterns for files to include. When absent, all files of the target language are checked.
excludearray of stringsnoGlob patterns to exclude. Takes precedence over include.

YAML severityHealth output levelExit code impact
infoP3 InfoNone
warningP2 WarningNone
errorP1 Errorforge health exits 1
criticalP0 Criticalforge health exits 1

Forge uses ast-grep patterns. The key wildcard syntax:

WildcardMatches
$VARAny single AST node
$$$VARZero or more AST nodes (a spread)
$_Any single node (anonymous wildcard)
Literal textMatches exactly

TypeScript / JavaScript examples:

# Match any console.log call
pattern: "console.log($$$ARGS)"
# Match any async function
pattern: "async function $NAME($$$PARAMS) { $$$BODY }"
# Match object spread in JSX props
pattern: "<$COMPONENT {...$PROPS} />"
# Match try without catch
pattern: "try { $$$BODY } finally { $$$FINALLY }"

Rust examples:

# Match any unwrap() call
pattern: "$EXPR.unwrap()"
language: rust
# Match functions returning Result
pattern: "fn $NAME($$$) -> Result<$$$, $$$> { $$$BODY }"
language: rust

Python examples:

# Match bare except clauses
pattern: "try:\n $$$\nexcept:\n $$$"
language: python

id: no-todo-prod
name: "No TODO comments in production code"
description: "TODO comments should be resolved before merging to main."
severity: warning
pattern: "// TODO: $$$TEXT"
language: typescript
include:
- "src/**"
exclude:
- "**/*.test.ts"
id: require-await-try-catch
name: "Async calls should be wrapped in try/catch"
severity: info
pattern: "const $VAR = await $CALL"
language: typescript
include:
- "src/**"

Flag direct database access outside the data layer

Section titled “Flag direct database access outside the data layer”
id: no-direct-db-access
name: "Database access outside data layer"
description: "Direct DB calls should only appear in src/data/. Use a repository function instead."
severity: error
pattern: "db.query($$$)"
language: typescript
include:
- "src/**"
exclude:
- "src/data/**"
id: no-empty-expect
name: "expect() calls must include an error message"
severity: warning
pattern: "$EXPR.expect(\"\")"
language: rust

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 team

The override applies to both built-in checks and plugin checks. The id in severity_overrides must match the plugin’s id field exactly.


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.