Plugin API
Canopy’s plugin system lets you define custom health checks for your codebase as YAML files. Each file is a pack of one or more checks that run alongside the built-in health checks and report findings at configurable severity levels.
Plugins work with all tiers, including Community.
Discovery
Section titled “Discovery”Place YAML plugin files in .canopy/plugins/ inside your repository:
<repo>/.canopy/plugins/ no-todo-comments.yaml require-error-handling.yaml asset-counts.yamlThe health runtime discovers and loads all *.yaml files in this directory automatically on the next canopy index or canopy ci run. No registration step is required. If the directory does not exist, nothing happens — plugins are purely opt-in.
Canopy also ships four first-party packs (React, Rust, Python, Supabase) embedded in the binary. canopy plugins list shows them, canopy plugins install <pack> copies one into .canopy/plugins/<pack>.yaml, and canopy plugins disable <pack> removes it. Installed packs are ordinary plugin files — edit them freely.
Plugin Schema
Section titled “Plugin Schema”Each plugin file defines a named pack of checks:
# Pack name. Appears in finding check_ids as plugin:<name>:<check-index>.# Required.name: no-console-log
# Semantic version of the plugin definition (informational, not enforced).# Required.version: "1.0"
# Default severity applied to any check that does not specify its own.# Optional. Values: P0 | P1 | P2 | info. Default: P1.severity: P1
# Ordered list of checks to execute.# Required.checks: - # The check variant. Values: file_exists | pattern_present | # pattern_absent | file_count | script type: pattern_absent
# Override severity for this individual check. Optional. severity: P2
# Message emitted in the finding when the check fails. Required. message: "console.log found — use the structured logger"
# pattern_absent fields: glob selects the files, pattern is a regex # that must NOT match any line, exclude lists path substrings to skip. glob: "src/**/*.ts" pattern: 'console\.log\s*\(' exclude: - ".test." - ".spec."Field Reference
Section titled “Field Reference”Pack-level fields:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Pack name. Findings carry check_id: plugin:<name>:<index>. |
version | string | yes | Informational semver for the plugin definition. |
severity | string | no | Default severity for checks without their own: P0, P1, P2, or info. Defaults to P1. |
checks | array | yes | Ordered list of checks. |
Every check takes type, message (required), and severity (optional override), plus its type-specific fields:
type | Asserts | Type-specific fields |
|---|---|---|
file_exists | Every listed file exists on disk | files — paths relative to the repo root |
pattern_present | Regex matches at least one line of a file | file — single file; pattern — regex |
pattern_absent | Regex matches no line in any selected file | glob — file glob (** for any depth); pattern — regex; exclude — optional path substrings to skip |
file_count | Directory holds at least N files with an extension | directory; extension (with leading dot, e.g. ".sql"); minimum |
script | Shell command exits with code 0 | command — passed verbatim to /bin/sh -c |
Severity Mapping
Section titled “Severity Mapping”YAML severity | Finding level | CI impact |
|---|---|---|
P0 | Critical | canopy ci exits non-zero with --fail-on-p0 |
P1 | Error | Annotated, build passes |
P2 | Warning | Annotated, build passes |
info | Info | Annotated, build passes |
In canopy ci --format json output, finding severities appear as critical, error, warning, and info.
Pattern Syntax
Section titled “Pattern Syntax”pattern fields are regular expressions (Rust regex syntax), evaluated one line at a time — a construct spanning multiple lines cannot be matched by a single pattern. Escape regex metacharacters when matching literal code.
TypeScript / JavaScript examples:
# Match any console.log callpattern: 'console\.log\s*\('
# Match explicit any annotationspattern: ':\s*any\b'
# Match process.exit callspattern: 'process\.exit\s*\('Rust examples:
# Match any unwrap() callpattern: '\.unwrap\s*\(\s*\)'
# Match panic! invocationspattern: 'panic!\s*\('Python examples:
# Match bare except clausespattern: '^\s*except\s*:'
# Match print() callspattern: '^\s*print\s*\('Example Plugins
Section titled “Example Plugins”Detect TODO comments in production code
Section titled “Detect TODO comments in production code”name: no-todo-prodversion: "1.0"checks: - type: pattern_absent severity: P2 message: "TODO comment — resolve before merging to main" glob: "src/**/*.ts" pattern: '//\s*TODO' exclude: - ".test."Flag direct database access outside the data layer
Section titled “Flag direct database access outside the data layer”name: no-direct-db-accessversion: "1.0"checks: - type: pattern_absent severity: P1 message: "Direct DB call outside src/data/ — use a repository function" glob: "src/**/*.ts" pattern: 'db\.query\s*\(' exclude: - "src/data/"Require migrations and project scaffolding
Section titled “Require migrations and project scaffolding”name: project-shapeversion: "1.0"severity: P2checks: - type: file_exists message: "Missing supabase/config.toml" files: - supabase/config.toml - type: file_count message: "No migrations under supabase/migrations/" directory: "supabase/migrations" extension: ".sql" minimum: 1Gate on an external linter
Section titled “Gate on an external linter”name: lint-gateversion: "1.0"checks: - type: script severity: P0 message: "eslint failed" command: "npx eslint src --max-warnings 0"Overriding Severity in team.yml
Section titled “Overriding Severity in team.yml”You can adjust a check’s severity for your team without editing the plugin file. Add overrides to .canopy/team.yml:
health_checks: enabled: [broken_imports, dead_exports, circular_deps, secrets] severity_overrides: no_console_log: P2The override applies to both built-in checks and plugin checks; the key must match the finding’s check id.
Error Handling
Section titled “Error Handling”A malformed plugin never blocks canopy index or canopy ci. If a plugin file has a YAML syntax error — or its pattern is not a valid regex — Canopy logs a warning, reports a P0 finding naming that plugin, and continues running the remaining plugins.