Getting Started with Codepol
This guide walks you through setting up codepol in your project.
Prerequisites
- A project with source code you want to enforce policy on
- For package-based usage (
@codepol/cli): Node.js 18+ and pnpm/npm/yarn - For standalone binary usage: no Node.js runtime is required in the target project
Choose a Usage Path
- Node-capable projects (recommended): install
@codepol/cliand runcodepol/npx codepol. - Non-Node projects: use the standalone binary bundle.
Step 1: Install Packages
Choose the packages you need:
Full Setup (Recommended)
# Install CLI (includes core)
pnpm add -D @codepol/cli
# For ESLint integration
pnpm add -D @codepol/plugin-eslint @codepol/plugin @typescript-eslint/utils
# For Biome-backed policy providers
pnpm add -D @codepol/plugin-biome
# For esbuild integration
pnpm add -D @codepol/plugin-esbuild esbuildMinimal Setup (ESLint only)
pnpm add -D @codepol/plugin-eslint @codepol/core @codepol/pluginStep 2: Create a Config File
Create codepol.toml in your project root:
# Optional: specify ESLint config path (auto-detected if not specified)
eslintConfigPath = "./eslint.config.mjs"
exclude = ["dist/**", "node_modules/**", "*.config.ts"]
[[plugins]]
id = "@codepol/plugin"
source = { kind = "builtin" }
[targets.typescript-src]
language = "typescript"
files = ["src/**/*.ts", "src/**/*.tsx"]
exclude = [
"**/*.spec.ts",
"**/*.test.ts",
"**/__mocks__/**",
"**/__tests__/**",
]
[[rules]]
id = "function-logging"
ruleId = "@codepol/plugin/require-logger-enter-exit"
description = "Ensure all functions have logger.enter/exit instrumentation"
targets = ["typescript-src"]
[rules.args.logger]
identifier = "logger"
enterMethod = "enter"
exitMethod = "exit"
import = { module = "@your-org/logger", named = "logger" }The config file is auto-discovered from your project root. Supported format:
codepol.toml
Tip: Multiple rules can reference the same target. See Policy Schema Reference for more details.
Tip: Multiple rules can reference the same target. See Policy Schema Reference for more details.
Step 3: Configure ESLint
Flat Config (eslint.config.js) - Recommended
import { eslintPluginCreate } from '@codepol/plugin-eslint';
import {
pluginBuiltinRegister,
policyPluginRulesGet,
providerRulesConfigGet,
} from '@codepol/core';
import codepolBuiltin from '@codepol/plugin';
import tseslint from 'typescript-eslint';
pluginBuiltinRegister('@codepol/plugin', codepolBuiltin);
const codepol = eslintPluginCreate(await policyPluginRulesGet());
export default [
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
plugins: {
codepol,
},
rules: {
...await providerRulesConfigGet('eslint'),
eqeqeq: 'error',
},
},
];Legacy Config (.eslintrc.cjs)
const { eslintPluginCreate } = require('@codepol/plugin-eslint');
const codepolPlugin = require('@codepol/plugin').default;
const tseslint = require('typescript-eslint');
module.exports = {
parser: tseslint.parser,
plugins: {
codepol: eslintPluginCreate(codepolPlugin),
},
rules: {
'codepol/require-logger-enter-exit': 'error',
eqeqeq: 'error',
},
};Rule keys use the ESLint plugin name codepol (for example, codepol/require-logger-enter-exit) even when the package is scoped.
Severity Precedence
When running ESLint directly (eslint .), your eslint.config.js rules apply.
When running codepol, severity is read from codepol.toml and passed via ESLint's overrideConfig, which takes precedence over your eslint.config.js for codepol rules.
Optional: Biome-backed providers
If a loaded rule exposes a platform = "biome" lint provider, codepol delegates to the Biome CLI: it runs biome lint on the JS/TS files matched by that policy rule’s targets (not every JS/TS file in the repo) and merges Biome’s diagnostics into the normal CLI output. codepol --fix runs biome lint --write for those same scoped files.
Rules:
- The provider selects how to invoke Biome (
biomeBin, optionalconfigPath, optionalextraArgs). It does not register custom rules inside Biome; enforcement comes from Biome’s own configuration. - Distinct provider configs are run as separate
biome lintinvocations (grouped by normalized config). The same rule must not declare two conflicting Biome provider configs. - Policy
severityandargsdo not currently change Biome’s behavior (unlike ESLint, where severity is passed throughoverrideConfig). Configure severity inbiome.json/ Biome CLI options instead.
Step 4: Create Your Logger
Create a logger module that matches your policy configuration:
// src/logger.ts
export const logger = {
enter(context?: Record<string, unknown>) {
console.log('[ENTER]', context);
},
exit(context?: Record<string, unknown>) {
console.log('[EXIT]', context);
},
};Update your codepol.toml to reference it:
[[plugins]]
id = "@codepol/plugin"
source = { kind = "builtin" }
[targets.src]
language = "typescript"
files = ["src/**/*.ts"]
[[rules]]
ruleId = "@codepol/plugin/require-logger-enter-exit"
targets = ["src"]
[rules.args.logger]
identifier = "logger"
enterMethod = "enter"
exitMethod = "exit"
import = { module = "./logger", named = "logger" }Step 5: Add NPM Scripts
Add to your package.json:
{
"scripts": {
"lint:policy": "codepol",
"lint:policy:fix": "codepol --fix",
"lint:policy:watch": "codepol --watch"
}
}Step 6: Run Your First Check
pnpm lint:policyIf you have functions without instrumentation, you'll see:
src/utils.ts
15:1 error Functions must invoke logger.enter and logger.exit codepol/require-logger-enter-exit
✖ 1 problem (1 error, 0 warnings)Step 7: Auto-Fix Violations
Run with --fix to apply any fixes provided by enabled rules:
pnpm lint:policy:fixBefore:
export interface User {
name: string;
}After:
export type User = {
name: string;
};Optional: esbuild Integration
If you use esbuild, add build-time enforcement:
// build.ts
import { build } from 'esbuild';
import { esbuildPluginCreate } from '@codepol/plugin-esbuild';
await build({
entryPoints: ['src/index.ts'],
bundle: true,
outdir: 'dist',
plugins: [
// Zero-config: auto-discovers codepol.toml
esbuildPluginCreate(),
],
});Or with explicit config path:
plugins: [
esbuildPluginCreate({
configPath: './config/codepol.toml',
}),
]Optional: CI Integration
GitHub Actions
name: Policy Check
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- run: pnpm install
- run: pnpm lint:policyPre-commit Hook
Using husky:
pnpm add -D husky
npx husky init
echo "pnpm lint:policy" > .husky/pre-commitUse the Standalone Binary (Recommended for Non-Node Projects)
For projects that do not use a JS/TS toolchain, use the standalone binary bundle. This is the recommended path for non-Node repositories. Download binaries from GitHub Releases (or CI artifacts) rather than committing dist-binary.
The release bundle contains the binary and required WASM files (must stay in the same directory):
codepoltree-sitter.wasmtree-sitter-typescript.wasmtree-sitter-tsx.wasmtree-sitter-python.wasm
Install to ~/.local/bin so codepol is available on PATH:
# Pick a release tag, for example v1.2.3
TAG=v1.2.3
# Download and install to ~/.local/bin
curl -fL "https://github.com/fruitiecutiepie/codepol/releases/download/${TAG}/codepol-binary-${TAG}-linux-x64.tar.gz" \
| tar -xz -C ~/.local/bin
# Alternative: download from a workflow artifact (requires gh auth)
# gh run download <run-id> --name codepol-binary --dir ~/.local/binMake sure ~/.local/bin is on your PATH (most systems include it by default).
Then in the consumer project:
# Run from project root (auto-discovers codepol.toml)
codepol
# Or pass explicit paths
codepol --config ./codepol.toml --eslint-config ./eslint.config.jsNext Steps
- Policy Schema Reference - All configuration options
- Creating Custom Plugins - Build your own rule plugins
- API Reference - Programmatic usage and type definitions