AST-based governance engine for enforcing module boundaries and architecture rules in Python and TypeScript codebases
Project description
code-governance
Stop spaghetti imports. Enforce module boundaries in Python and TypeScript.
Before / After
| Without governance | With governance |
|---|---|
api/ imports from billing/, db/, auth/, utils/, migrations/... |
api/ has cannot_depend_on = ["billing", "migrations"] — forbidden imports blocked |
| One refactor breaks 14 files across 6 modules | Boundaries are explicit, changes stay local |
| New dev: "Can I import this here?" "Uhh... maybe?" | Config says yes or no. CI enforces it. |
Setup
Option 1: With Claude Code (recommended)
Install the plugin — Claude handles everything interactively. Run each command separately — pasting them all at once will concatenate into a single broken URL:
/plugin marketplace add useparadigm/code-governance-plugin
/plugin install code-governance
/reload-plugins
Then run:
/governance-init
Without
/reload-plugins, the new commands won't be registered yet and/governance-initwill fail withUnknown command.
What happens:
- Claude installs
code-governancein your project - Scans your codebase — discovers modules, maps every import
- Shows you the dependency map: "I found 8 modules.
apiimports fromdbdirectly — should it?" - You discuss architecture: which dependencies are intentional, which are spaghetti
- Claude creates
governance.tomlbased on your answers - Enables rules:
no_cycles,enforce_cannot_depend_on, optionallyenforce_layers - If existing violations: "Want a baseline? I'll accept these and only fail on new ones."
- Sets up GitHub Actions — CI check on every PR +
/governance fixcommand - Done. Every PR is now gated on architecture rules.
After setup, two more commands available:
| Command | What it does |
|---|---|
/governance-check |
Run checks, explain violations, fix them interactively |
/governance-audit |
Architecture health review — metrics, hotspots, suggestions |
Option 2: Manual
pip install code-governance
Quick scan — no config needed, instant results:
$ governance-ast --auto src/
Governance Report (python)
Modules: 8 | Files scanned: 47
Violations (1):
[E] [no_cycles] Circular dependency: payments -> notifications -> payments
FAILED
Works on TypeScript too — language auto-detected from source:
$ governance-ast --auto src/
Governance Report (typescript)
Modules: 6 | Files scanned: 82
...
.ts, .tsx, .js, .jsx, .mts, .cts, .mjs, .cjs all supported. tsconfig.json path aliases (@/*, extends chains) are honored.
Full setup — generate config, review, enforce:
# Generate config: detect modules + seed cannot_depend_on by locking down
# every module pair that does not currently import from each other.
governance-ast --generate --source-root src/
# See the dependency map
governance-ast --discover
# Edit governance.toml — remove dependencies you don't want, add layers
# Then enforce:
governance-ast
Set up CI — add to .github/workflows/governance.yml:
- uses: actions/checkout@v4
- name: Governance
uses: useparadigm/code-governance@main
with:
config: governance.toml
diff: origin/main
See governance-fix workflow to enable the /governance fix PR command.
GitLab CI — add to .gitlab-ci.yml:
governance:
image: python:3.12-slim
variables:
GIT_DEPTH: 0
before_script:
- pip install code-governance
script:
- governance-ast --config governance.toml --diff origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
See GitLab MR comments to post the report as an MR note.
What it catches
| Rule | Example |
|---|---|
enforce_cannot_depend_on |
api imports billing but billing is in cannot_depend_on |
no_cycles |
payments -> notifications -> payments |
enforce_layers |
db (infrastructure) imports from api (presentation) |
max_public_surface |
80% of core's symbols used externally — too exposed |
min_cohesion |
utils imports 90% from other modules — grab-bag module |
What happens in CI
Every PR gets a governance comment:
❌ Governance — 1 violation, 1 new module
🔴 🔗 core: Forbidden dependency: 'core' imports 'db' (cannot_depend_on: ['db'])
core/service.py:2
📦 New module exporters (exporters/) — not in governance.toml
Reply /governance fix to apply.
🤖 AI: The core→db import creates tight coupling. Extract shared
types into a common module, or remove "db" from core's cannot_depend_on.
New module detected? Reply /governance fix — bot adds it to config, commits to your branch.
Violation? Fix the import or update governance.toml. Use /governance-check in Claude Code for interactive help.
Pre-commit
repos:
- repo: https://github.com/useparadigm/code-governance
rev: main
hooks:
- id: governance-check
- id: governance-diff
Adopting on legacy codebases
governance-ast --save-baseline .governance-baseline.json
governance-ast --baseline .governance-baseline.json
Accept existing violations. Only fail on new ones.
Transitive detection
governance-ast --transitive
Detects indirect violations through dependency chains. If api has cannot_depend_on = ["db"] and api → service → db, the transitive check catches it. Also works with layer enforcement. Enable permanently in config with transitive = true under [rules].
LLM advice
export OPENAI_API_KEY=sk-... # or ANTHROPIC_API_KEY
governance-ast --advise
Analyzes violations and suggests: accept the dependency, restructure the code, or extract a shared module. Works with OpenAI and Anthropic.
HTML report
governance-ast --format html > report.html
Self-contained dependency matrix with module metrics. Drop any governance JSON into it.
Comparison
| code-governance | tach | import-linter | |
|---|---|---|---|
| Zero-config scan | --auto — instant |
No | No |
| Config generation | --generate from source |
tach init (interactive TUI) |
Manual |
| LLM advice | --advise — explains + suggests fix |
No | No |
| CI comments | PR comments with explanations | No | No |
| Auto-fix from PR | /governance fix |
No | No |
| AI agent plugin | Claude Code plugin | No | No |
| Diff mode | --diff HEAD |
No | No |
| Baseline | Accept existing, fail on new | No | No |
| Module metrics | Cohesion, surface, symbols | No | No |
| HTML report | Dependency matrix viewer | tach show |
Browser UI |
| JSON output | Yes | Yes | No |
| Scans source directly | Yes | Yes | No — must be importable |
| Interface enforcement | No | Yes | No |
| Transitive imports | Full chain (--transitive) |
Direct only | Full chain |
| Forbidden imports | cannot_depend_on |
cannot_depend_on |
Forbidden contract |
| Visibility control | No | Yes | No |
| Speed (Django) | ~1.2s | Sub-second (Rust) | ~0.1s (grimp) |
| Config syntax | Simple TOML lists | TOML with regex | INI or TOML |
| Maintenance | Active | Abandoned by original team | Active, slow |
Choose tach if you need interface enforcement or visibility control (note: unmaintained). Choose code-governance if you want transitive detection, AI-guided setup, CI integration, or zero-config scanning.
Governance fix workflow
Add to .github/workflows/governance-fix.yml:
name: Governance Fix
on:
issue_comment:
types: [created]
permissions:
contents: write
pull-requests: write
jobs:
apply-fix:
name: Apply config fix
runs-on: ubuntu-latest
if: |
github.event.issue.pull_request &&
contains(github.event.comment.body, '/governance fix')
steps:
- name: Acknowledge
env:
GH_TOKEN: ${{ github.token }}
run: |
gh api repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions \
-X POST -f content=eyes
- uses: actions/checkout@v4
- name: Checkout PR branch
env:
GH_TOKEN: ${{ github.token }}
run: gh pr checkout ${{ github.event.issue.number }}
- name: Find and apply fix
id: fix
env:
GH_TOKEN: ${{ github.token }}
run: |
PAYLOAD=$(gh api repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments \
--jq '.[] | select(.body | contains("<!-- governance-fix:")) | .body' \
| grep -oP '<!-- governance-fix:\K[A-Za-z0-9+/=]+' | tail -1)
if [ -z "$PAYLOAD" ]; then
gh api repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions \
-X POST -f content=confused
exit 1
fi
DECODED=$(echo "$PAYLOAD" | base64 -d)
CONFIG_PATH=$(echo "$DECODED" | python3 -c "import sys,json; print(json.load(sys.stdin)['config_path'])")
echo "$DECODED" | python3 -c "import sys,json; print(json.load(sys.stdin)['updated_config'])" > "$CONFIG_PATH"
pip install code-governance
echo "Config updated"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add "$CONFIG_PATH"
git diff --cached --quiet && exit 0
git commit -m "governance: add new modules to config"
git push
echo "sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Confirm
if: steps.fix.outputs.sha
env:
GH_TOKEN: ${{ github.token }}
run: |
gh api repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions \
-X POST -f content='+1'
gh pr comment ${{ github.event.issue.number }} \
--body "✅ Config fix applied in \`${{ steps.fix.outputs.sha }}\`. Re-run checks to verify."
GitLab MR comments
Posts the governance report as a note on the merge request. Requires a Project Access Token with api scope, exposed as masked CI variable GOVERNANCE_GITLAB_TOKEN.
governance:
image: python:3.12-slim
variables:
GIT_DEPTH: 0
before_script:
- pip install code-governance
- apt-get update && apt-get install -y --no-install-recommends curl jq && rm -rf /var/lib/apt/lists/*
script:
- set +e
- governance-ci-report --config governance.toml --json > report.json
- set -e
- PASSED=$(jq -r '.passed' report.json)
- jq -r '.markdown' report.json | tee report.md
- |
if [ "$CI_PIPELINE_SOURCE" = "merge_request_event" ] && [ -n "$GOVERNANCE_GITLAB_TOKEN" ]; then
BODY=$(jq -Rs . < report.md)
curl -sf --request POST \
--header "PRIVATE-TOKEN: $GOVERNANCE_GITLAB_TOKEN" \
--header "Content-Type: application/json" \
--data "{\"body\": $BODY}" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes"
fi
- test "$PASSED" = "true"
artifacts:
when: always
paths: [report.json, report.md]
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
/governance fix is GitHub-only for now — GitLab equivalent would need a Note webhook listener.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file code_governance-0.4.0.tar.gz.
File metadata
- Download URL: code_governance-0.4.0.tar.gz
- Upload date:
- Size: 235.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
81ab00ae07e08dfe67d648d26e65aa210bdb3fc11684cdb0c1e505aa3d9bfc0e
|
|
| MD5 |
17e0b63ee0631d47f22ea0ab28fca4e6
|
|
| BLAKE2b-256 |
f72814efb7fc016fbe40b140713f52ddda3036af41d0f89d7f1bcadb35ddf76e
|
File details
Details for the file code_governance-0.4.0-py3-none-any.whl.
File metadata
- Download URL: code_governance-0.4.0-py3-none-any.whl
- Upload date:
- Size: 45.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
78d1604ddcf2630adfa4b7599f467c80f3dbfcbb1177ab4f658de70151ad0273
|
|
| MD5 |
0ce25ca51f71d983d4d132cb31a3f226
|
|
| BLAKE2b-256 |
8deaf787e4251834b3344e7fa91ddbb7b7cce3d19539069b7d93df4c8b47d294
|