API Reference
Programmatic API for integrating codepol into your tools and scripts.
@codepol/core
The core package provides policy loading, checking, and formatting utilities.
Installation
pnpm add @codepol/coreTypes
import type {
// Config types
CodepolConfig,
CodepolConfigOptions,
ConfigFileResult,
// Policy types
PolicyFile,
PolicyRule,
PolicyRuleTarget,
PolicyTargetMap,
PolicyRuleTargetContext,
LoggerConfig,
LoggerImportConfig,
LintSeverity,
PolicyViolation,
PolicyViolationFix,
TreeCheckProvider,
PolicyPluginCapabilities,
PluginRuleConfig,
CodepolPluginRule,
PluginRule,
PolicyPluginDeclaration,
PolicyCheckContext,
LintProvider,
LintProviderContext,
EslintProviderConfig,
FixProvider,
FixProviderContext,
RuleMatch,
PolicyCheckOptions,
PolicyCheckResult,
// Adapter types
LintDiagnostic,
TreeCheckAdapterOptions,
TreeCheckLintAdapter,
// Result
Result,
// Parser / Language
Lang,
// Plugin loading
PolicyPluginsMap,
// Semantic Index types
SymbolId,
ScopeId,
SymbolKind,
ScopeKind,
ByteRange,
SymbolRecord,
ScopeRecord,
RelationRecord,
DefinesRelation,
ContainsRelation,
ReferencesRelation,
ImportsRelation,
CallsRelation,
ImportBindingRelation,
ExportsRelation,
TypeRelation,
SymbolFilter,
IndexCapabilities,
FlowNodeId,
FlowNode,
FlowEdge,
FlowGraph,
FlowNodeKind,
// Module resolution
ModuleResolveOptions,
// Index query
ProjectIndex,
// Module graph
ModuleGraph,
// Index builder
IndexBuildOptions,
IndexBuildResult,
// Index store
FileIndexDelta,
} from '@codepol/core';Policy rules split semantics (meaning) from language targets. PolicyRule defines the rule metadata and plugin reference (ruleId), while PolicyRuleTarget declares the language adapter or parser plus its file globs. Rules reference targets by name via rule.targets (an array of strings), pointing to entries in the top-level PolicyFile.targets map. This lets a single rule apply across multiple targets without duplicating configuration.
Config Loading
Codepol uses a unified config file that is auto-discovered from your project root.
defineConfig
Type-safe helper for building codepol config objects programmatically (e.g. in tests or advanced hosts). Runtime config is loaded from codepol.toml; this helper does not replace it.
import { defineConfig } from '@codepol/core';
const config = defineConfig({
eslintConfigPath: './eslint.config.ts',
plugins: [
{ id: '@codepol/plugin', source: { kind: 'builtin' } },
],
targets: { /* ... */ },
rules: [ /* ... */ ],
});configGet
Discovers and loads the codepol config file by walking up from the current directory.
async function configGet(cwd?: string): Promise<ConfigFileResult>Parameters:
cwd: Working directory to start search from (default:process.cwd())
Returns: Object with config (CodepolConfig) and configPath (string)
Throws: If no config file is found
Example:
import { configGet } from '@codepol/core';
const { config, configPath } = await configGet();
console.log(`Loaded config from: ${configPath}`);
console.log(`Rules: ${config.rules.length}`);Config file discovery order:
codepol.toml
configGetFromPath
Loads a config file from a specific path. Use this when you have an explicit path (e.g., from --config flag).
async function configGetFromPath(configPath: string): Promise<ConfigFileResult>Parameters:
configPath: Path to the config file (absolute or relative)
Returns: Object with config (CodepolConfig) and configPath (string)
Example:
import { configGetFromPath } from '@codepol/core';
const { config } = await configGetFromPath('./config/codepol.toml');configFileDiscover
Synchronously walks up from the starting directory to find a config file.
function configFileDiscover(startDir: string): string | nullParameters:
startDir: Directory to start searching from
Returns: Absolute path to the config file, or null if not found
configGetSync
Synchronous version of configGet. Used by the ESLint adapter, which requires sync execution.
function configGetSync(cwd?: string): ConfigFileResultParameters:
cwd: Working directory to start search from (default:process.cwd())
Returns: Object with config (CodepolConfig) and configPath (string)
Throws: If no config file is found
Example:
import { configGetSync } from '@codepol/core';
const { config, configPath } = configGetSync();configGetFromPathSync
Synchronous version of configGetFromPath. Used by the ESLint adapter, which requires sync execution.
function configGetFromPathSync(configPath: string): ConfigFileResultParameters:
configPath: Path to the config file (absolute or relative)
Returns: Object with config (CodepolConfig) and configPath (string)
Throws: If the config file does not exist
Example:
import { configGetFromPathSync } from '@codepol/core';
const { config } = configGetFromPathSync('./config/codepol.toml');configCacheClear
Clears the in-memory config cache. Useful for testing or when config files change at runtime.
function configCacheClear(): voidExample:
import { configCacheClear } from '@codepol/core';
configCacheClear();CodepolConfig
Full codepol configuration type combining policy definition and runtime options.
type CodepolConfig = PolicyFile & CodepolConfigOptions;
type CodepolConfigOptions = {
/** Path to ESLint config (auto-detected if not specified) */
eslintConfigPath?: string;
};policyFileGet (deprecated)
Loads and parses a JSON config file. Deprecated: Use configGet() or configGetFromPath() instead, which support TOML config files (codepol.toml).
function policyFileGet(policyPath: string): PolicyFileParameters:
policyPath: Path to the JSON config file (absolute or relative)
Returns: Parsed PolicyFile object
Example:
// Prefer configGet() for new code:
import { configGet } from '@codepol/core';
const { config } = await configGet();
// Legacy JSON support:
import { policyFileGet } from '@codepol/core';
const policy = policyFileGet('./legacy-config.json');providerRulesConfigGet
Generates lint provider rules config from the codepol config. Users spread this into their lint config (e.g., eslint.config.js).
async function providerRulesConfigGet(
provider: 'eslint',
configPath?: string
): Promise<Record<string, unknown>>Parameters:
provider: The lint provider platform ('eslint')configPath: Path to config file (auto-discovered if not specified)
Returns: Rules config object for the lint provider
Example:
// eslint.config.js
import { eslintPluginCreate } from '@codepol/plugin-eslint';
import {
pluginBuiltinRegister,
policyPluginRulesGet,
providerRulesConfigGet,
} from '@codepol/core';
import codepolBuiltin from '@codepol/plugin';
pluginBuiltinRegister('@codepol/plugin', codepolBuiltin);
const codepol = eslintPluginCreate(await policyPluginRulesGet());
export default [{
plugins: { codepol },
rules: {
...await providerRulesConfigGet('eslint'),
'no-console': 'warn',
},
}];Severity is read from the config per rule (default: 'error'):
[[rules]]
ruleId = "@codepol/plugin/require-logger-enter-exit"
severity = "warn"
targets = ["typescript"]Filtering by Provider
Use providers to control which providers a rule applies to:
[[rules]]
ruleId = "@codepol/plugin/require-logger-enter-exit"
providers = ["tree-sitter"]
targets = ["typescript"]If providers is omitted or empty, the rule applies to all providers. This is useful when you want a rule to only run during codepol check (tree-sitter) but not in ESLint, or vice versa.
Manual ESLint Configuration
providerRulesConfigGet is a convenience helper, not a requirement. You can manually configure codepol rules in your ESLint config:
// eslint.config.js
import codepol from '@codepol/plugin-eslint';
export default [{
plugins: { codepol },
rules: {
'codepol/require-logger-enter-exit': ['error', { /* options */ }],
'my-plugin/custom-rule': 'warn',
},
}];This works when running ESLint directly (eslint .).
codepol check CLI Override
When using codepol check, the CLI generates rules from your config and passes them via ESLint's overrideConfig, which has highest precedence. This means rules defined in codepol.toml will use severity from the config, overriding your eslint.config.js settings for those same rules.
If you want full control over ESLint configuration, run ESLint directly instead of using codepol check.
Parser and Language Registration
Tree-sitter WASM parsers must be initialized before any policy checking or index building.
parserInit
Initializes the web-tree-sitter WASM runtime and loads registered language grammars. Must be called before any scanning or indexing operations. Safe to call multiple times (subsequent calls load newly registered languages only).
async function parserInit(): Promise<void>Example:
import { parserInit, langAdd } from '@codepol/core';
langAdd({ langId: 'typescript', fileExtensions: ['.ts'] });
langAdd({ langId: 'tsx', fileExtensions: ['.tsx'] });
await parserInit();parserGetForFile
Creates a Tree-sitter parser configured for the given file's language.
function parserGetForFile(filePath: string): Result<Parser, string>Parameters:
filePath: Absolute path to the file
Returns: Ok(Parser) on success, Err(string) if the parser is not initialized or no language is registered for the file extension
Example:
import { parserGetForFile, isOk } from '@codepol/core';
const result = parserGetForFile('/path/to/file.ts');
if (isOk(result)) {
const tree = result.Ok.parse(sourceCode);
}Lang
Language registration configuration.
type Lang = {
langId: string;
/** Path to WASM file. If omitted, uses bundled wasm/tree-sitter-{langId}.wasm */
wasmPath?: string;
fileExtensions: string[];
};langAdd
Registers a language with its WASM grammar and file extensions. Merges extensions if the language is already registered. Must be called before parserInit().
function langAdd(lang: Lang): voidParameters:
lang: Language registration config
Throws: If langId is empty, no extensions are provided, an extension is already registered to a different language, or the language is re-registered with a different wasmPath
Example:
import { langAdd } from '@codepol/core';
langAdd({ langId: 'typescript', fileExtensions: ['.ts'] });
langAdd({ langId: 'tsx', fileExtensions: ['.tsx'] });
// Custom WASM path
langAdd({
langId: 'python',
wasmPath: '/path/to/tree-sitter-python.wasm',
fileExtensions: ['.py'],
});langsGet
Returns all registered languages.
function langsGet(): Required<Lang>[]Returns: Array of language registrations with all fields populated (including resolved wasmPath)
wasmPathGet
Resolves the absolute path to a bundled WASM grammar file.
function wasmPathGet(grammarName: string): stringParameters:
grammarName: Grammar name (e.g.,'tree-sitter-typescript')
Returns: Absolute path to the WASM file
langIdGetForFile
Returns the registered language ID for a file based on its extension.
function langIdGetForFile(filePath: string): string | nullParameters:
filePath: File path (can be relative or absolute)
Returns: Language ID (e.g., 'typescript') or null if the extension is not registered
policyRuleTargetsResolve
Resolves the targets for a policy rule by looking up each target name in the policy's named targets map.
function policyRuleTargetsResolve(
rule: PolicyRule,
policy: PolicyFile
): PolicyRuleTarget[]Parameters:
rule: The policy rule to resolve targets forpolicy: The policy file containing named targets
Returns: Array of resolved PolicyRuleTarget objects
Throws: If any target name in rule.targets doesn't exist in policy.targets
Example:
import { configGet, policyRuleTargetsResolve } from '@codepol/core';
const { config } = await configGet();
for (const rule of config.rules) {
const targets = policyRuleTargetsResolve(rule, config);
console.log(`Rule ${rule.id} has ${targets.length} target(s)`);
for (const target of targets) {
console.log(` - ${target.language}: ${target.files.join(', ')}`);
}
}API naming note
@codepol/core exports policyFileGet-style names (policyFileGet, ruleMatchesGet, policyViolationsGetFromDir, etc.). If you rely on loadPolicy-style names in existing code, update your imports to the canonical API names listed below.
ruleMatchesGet
Collects all files matching each policy rule.
function ruleMatchesGet(
policy: PolicyFile,
cwd: string
): Promise<RuleMatch[]>Parameters:
policy: The loaded policy objectcwd: Working directory for resolving glob patterns
Returns: Array of RuleMatch objects
Example:
import { configGet, ruleMatchesGet } from '@codepol/core';
const { config } = await configGet();
const matches = await ruleMatchesGet(config, process.cwd());
for (const match of matches) {
console.log(`Rule: ${match.rule.id}`);
console.log(`Target language: ${match.target.language}`);
console.log(`Files: ${match.files.length}`);
}policyFileGetChecked
Determines if a file should be checked against the policy.
function policyFileGetChecked(
policy: PolicyFile,
filePath: string,
cwd: string
): booleanParameters:
policy: The loaded policy objectfilePath: Absolute path to the filecwd: Working directory for resolving glob patterns
Returns: true if the file should be checked
Example:
import { configGet, policyFileGetChecked } from '@codepol/core';
const { config } = await configGet();
const covered = policyFileGetChecked(
config,
'/path/to/file.ts',
process.cwd()
);globPatternsGetMatchAny
Checks if any glob pattern in the list matches the given file path. Used internally by policy matching and available for custom tooling.
function globPatternsGetMatchAny(
patterns: string[] | undefined,
relativeFile: string
): booleanParameters:
patterns: Array of glob patterns (returnsfalseif undefined or empty)relativeFile: Relative file path to check
Returns: true if any pattern matches
Example:
import { globPatternsGetMatchAny } from '@codepol/core';
globPatternsGetMatchAny(['src/**/*.ts', 'lib/**/*.ts'], 'src/utils.ts');
// true
globPatternsGetMatchAny(undefined, 'src/utils.ts');
// falseruleTargetMatchesLanguage
Checks if a file matches the language specified in a rule target.
function ruleTargetMatchesLanguage(
target: PolicyRuleTarget,
filePath: string
): booleanParameters:
target: The policy rule target containing the languagefilePath: File path to check (relative or absolute)
Returns: true if the file matches the target language
Language matching rules:
"tsx"-- matches.tsxfiles only"typescript"-- matches.tsand.tsxfiles- Any other language -- always returns
true(no extension filtering)
policyViolationsGetForFile
Checks a single file for policy violations using Tree-sitter.
function policyViolationsGetForFile(
filePath: string,
rule: PolicyRule,
target: PolicyRuleTarget,
policy: PolicyFile,
pluginsMap: PolicyPluginsMap,
dir: string
): Result<PolicyViolation[], string>Parameters:
filePath: Absolute path to the filerule: The policy rule being checkedtarget: The rule target (language/parser/glob configuration)policy: The loaded policy objectpluginsMap: Loaded policy plugins (frompolicyPluginsGet)dir: Working directory for resolving paths and plugin context
Returns: Result containing violations or an error message
Example:
import {
configGet,
policyPluginsGet,
policyRuleTargetsResolve,
policyViolationsGetForFile,
} from '@codepol/core';
const { config } = await configGet();
const pluginsResult = await policyPluginsGet(config, process.cwd());
if ('Err' in pluginsResult) {
throw new Error(pluginsResult.Err);
}
const rule = config.rules[0];
const targets = policyRuleTargetsResolve(rule, config);
const target = targets[0];
const violationsResult = policyViolationsGetForFile(
'/path/to/file.ts',
rule,
target,
config,
pluginsResult.Ok,
process.cwd()
);
if ('Ok' in violationsResult) {
console.log(violationsResult.Ok.length);
}policyViolationsGetFromDir
Checks all files matching the policy for violations.
function policyViolationsGetFromDir(
policy: PolicyFile,
cwd: string
): Promise<Result<PolicyViolation[], string>>Parameters:
policy: The loaded policy objectcwd: Working directory for resolving patterns
Returns: Result containing all violations or an error message
Example:
import { configGet, policyViolationsGetFromDir } from '@codepol/core';
const { config } = await configGet();
const violationsResult = await policyViolationsGetFromDir(config, process.cwd());
if ('Ok' in violationsResult) {
for (const v of violationsResult.Ok) {
console.log(`${v.filePath}:${v.line}:${v.column} - ${v.message}`);
}
}policyCheck
Runs complete policy checks (Tree-sitter checking).
function policyCheck(
options: PolicyCheckOptions
): Promise<Result<PolicyCheckResult, string>>Parameters:
type PolicyCheckOptions = {
configPath?: string; // Path to config file (auto-discovered if not specified)
cwd?: string;
};Returns: Result containing the check output or an error message.
The success payload uses:
type PolicyCheckResult = {
policy: PolicyFile;
files: string[];
treeViolations: PolicyViolation[];
};Example:
import { policyCheck, policyViolationsGetOutputPretty } from '@codepol/core';
// Auto-discovers config from current directory
const result = await policyCheck({});
// Or with explicit config path
const result2 = await policyCheck({
configPath: './config/codepol.toml',
});
if ('Ok' in result) {
console.log(`Checked ${result.Ok.files.length} files`);
console.log(`Found ${result.Ok.treeViolations.length} violations`);
if (result.Ok.treeViolations.length > 0) {
console.log(
policyViolationsGetOutputPretty(result.Ok.treeViolations, process.cwd())
);
}
}policyViolationsGetOutputPretty
Formats violations into a human-readable string.
function policyViolationsGetOutputPretty(
violations: PolicyViolation[],
cwd: string
): stringParameters:
violations: Array of violationscwd: Working directory for relative paths
Returns: Formatted string (empty if no violations)
Example:
import { policyViolationsGetOutputPretty } from '@codepol/core';
const output = policyViolationsGetOutputPretty(violations, process.cwd());
if (output) {
console.log(output);
}
// Output:
// src/utils.ts:15:1 Function foo is missing logger.enter & logger.exit [function-logging]Plugin Loading
PolicyPluginsMap
Map of loaded plugin rules, keyed by resolved (namespaced) rule ID.
type PolicyPluginsMap = Map<string, PluginRule>;policyPluginsGet
Loads, validates, and namespaces all plugins declared in the policy. Built-in plugins are resolved from the in-process registry; process plugins are invoked as subprocesses via the JSON protocol. All policy rule references are validated against the loaded plugins.
async function policyPluginsGet(
policy: PolicyFile,
cwd: string,
options?: { configPath?: string }
): Promise<Result<PolicyPluginsMap, string>>Parameters:
policy: The loaded policy definitioncwd: Working directory for resolving plugin module specifiersoptions.configPath: Path to the config file. Process plugins resolve relativecommandpaths from this location; omitting it falls back tocwd.
Returns: Ok(PolicyPluginsMap) on success, Err(string) if a plugin fails to load, a rule references an unknown plugin, or language support validation fails
Example:
import { configGet, policyPluginsGet, isErr } from '@codepol/core';
const { config } = await configGet();
const result = await policyPluginsGet(config, process.cwd());
if (isErr(result)) {
console.error(result.Err);
process.exit(1);
}
const pluginsMap = result.Ok;
console.log(`Loaded ${pluginsMap.size} rule(s)`);pluginGetForRule
Looks up a plugin by rule ID. Supports both fully-qualified IDs (@scope/plugin/rule) and short IDs (rule), matching by suffix.
function pluginGetForRule(
pluginsMap: PolicyPluginsMap,
ruleId: string
): { plugin: PluginRule; resolvedId: string } | undefinedParameters:
pluginsMap: Loaded plugins map (frompolicyPluginsGet)ruleId: Rule ID (full or short)
Returns: The matched plugin and its resolved ID, or undefined if not found or ambiguous (multiple short-ID matches)
Example:
import { pluginGetForRule } from '@codepol/core';
const lookup = pluginGetForRule(pluginsMap, 'require-logger-enter-exit');
if (lookup) {
console.log(lookup.resolvedId);
// '@codepol/plugin/require-logger-enter-exit'
}Tree-Check Adapter Types
The adapter layer enables converting TreeCheckProvider implementations into lint provider rules. This is a provider-agnostic design: ESLint is the first supported platform, with Biome/Ruff/others possible in the future.
LintDiagnostic
Platform-agnostic lint diagnostic that any lint provider can consume.
type PolicyDiagnosticLocation = {
filePath: string;
line: number;
column: number;
endLine?: number;
endColumn?: number;
message?: string;
};
type LintDiagnostic = {
message: string; // Human-readable message
line: number; // 1-based line number
column: number; // 1-based column number
endLine?: number; // Optional end line
endColumn?: number; // Optional end column
ruleId: string; // Rule ID that produced this diagnostic
severity: 'error' | 'warning' | 'info';
relatedLocations?: PolicyDiagnosticLocation[]; // Optional extra spans
};TreeCheckAdapterOptions
Options for adapting a TreeCheckProvider to a lint provider rule.
type TreeCheckAdapterOptions = {
ruleName?: string; // Rule name for the generated lint rule
ruleUrl?: string; // URL to rule documentation
severity?: 'error' | 'warning'; // Default severity (default: 'error')
};Note: Config path is now auto-discovered via
configFileDiscover(). Pass explicit paths through ESLint rule options if needed.
TreeCheckLintAdapter
Adapter contract for converting TreeCheckProvider to lint provider rules.
type TreeCheckLintAdapter<TRule> = {
platform: string; // Platform identifier (e.g., 'eslint', 'biome')
adapt: (provider: CodepolPluginRule, options?: TreeCheckAdapterOptions) => TRule;
};violationToLintDiagnostic
Converts a PolicyViolation to a platform-agnostic LintDiagnostic.
function violationToLintDiagnostic(
violation: PolicyViolation,
severity?: 'error' | 'warning' | 'info'
): LintDiagnosticParameters:
violation: The policy violation to convertseverity: Severity level to assign (default:'error')
Returns: A LintDiagnostic representing the violation
Example:
import { violationToLintDiagnostic } from '@codepol/core';
const violation = {
ruleId: 'require-logger',
filePath: '/src/foo.ts',
message: 'Missing logger.enter()',
line: 10,
column: 5,
};
const diagnostic = violationToLintDiagnostic(violation);
// { message: 'Missing logger.enter()', line: 10, column: 5, ruleId: 'require-logger', severity: 'error' }violationsToLintDiagnostics
Batch converts an array of PolicyViolation to LintDiagnostic.
function violationsToLintDiagnostics(
violations: PolicyViolation[],
severity?: 'error' | 'warning' | 'info'
): LintDiagnostic[]Parameters:
violations: Array of policy violations to convertseverity: Severity level to assign (default:'error')
Returns: Array of LintDiagnostic objects
Plugin Authoring
Primitives for creating codepol rule plugins.
pluginRuleNew
Factory that validates and creates a CodepolPluginRule. This is the only way to create a rule plugin -- direct object literals will not type-check due to internal branding.
function pluginRuleNew(config: PluginRuleConfig): CodepolPluginRuleParameters:
config: Rule plugin configuration (seePluginRuleConfig)
Returns: A branded CodepolPluginRule
Throws: If the rule ID contains / (reserved for namespacing)
Example:
import { pluginRuleNew, treeCheckProviderNew } from '@codepol/core';
const myProvider = treeCheckProviderNew({
languages: ['typescript', 'tsx'],
check: (rule, context) => {
const violations = [];
// ... check logic ...
return violations;
},
});
export const myRule = pluginRuleNew({
id: 'no-todo-comments',
capabilities: {
treeCheckProvider: myProvider,
},
});
export default [myRule];PluginRuleConfig
Input configuration for creating a rule plugin.
type PluginRuleConfig = {
/** Must NOT contain '/' -- reserved for namespacing (e.g., "my-rule" becomes "@scope/plugin/my-rule") */
id: string;
/** Capability bundle for this rule */
capabilities: PolicyPluginCapabilities;
};PolicyViolation
A single policy finding. Primary position uses line / column; optional endLine / endColumn close the range; relatedLocations lists additional spans (see PolicyDiagnosticLocation under Tree-Check Adapter Types).
type PolicyViolation = {
ruleId: string;
filePath: string;
message: string;
line: number;
column: number;
endLine?: number;
endColumn?: number;
relatedLocations?: PolicyDiagnosticLocation[];
fix?: PolicyViolationFix;
};PolicyViolationFix
Fix data for an auto-fixable violation. Provides the byte range and replacement text for ESLint/IDE inline fixes.
type PolicyViolationFix = {
/** Byte offset range [start, end) in the source text to replace */
byteRange: ByteRange;
/** Replacement text (can be empty to delete the range) */
text: string;
};Violations with a fix field are auto-fixable when running through ESLint with --fix or via IDE quick-fix actions.
FixProviderContext
Context passed to fix providers when applying fixes.
type FixProviderContext = {
cwd: string;
policy: PolicyFile;
configPath: string;
files: string[];
ruleTargets?: PolicyRuleTargetContext[];
};TreeCheckFn
Consumer-facing check function type. Returns a plain violations array; errors are thrown as exceptions (the treeCheckProviderNew factory wraps this with resultFrom to produce Result).
type TreeCheckFn = (
rule: PolicyRule,
context: PolicyCheckContext
) => PolicyViolation[];treeCheckProviderNew
Factory for creating a TreeCheckProvider from a plain check function. Wraps the function with resultFrom so that thrown exceptions are converted to Result.Err instead of propagating.
function treeCheckProviderNew(config: {
languages: string[];
check: TreeCheckFn;
}): TreeCheckProviderParameters:
config.languages: Languages this provider supports (e.g.,['typescript', 'tsx'])config.check: Check function that receives a rule and context, returns violations
Returns: A TreeCheckProvider suitable for use in PluginRuleConfig.capabilities
Example:
import { treeCheckProviderNew } from '@codepol/core';
const provider = treeCheckProviderNew({
languages: ['typescript', 'tsx'],
check: (rule, context) => {
const violations = [];
// ... tree-sitter analysis ...
return violations;
},
});eslintProviderCreate
Factory for creating LintProvider objects with platform: 'eslint'.
function eslintProviderCreate(config: {
languages: string[];
pluginName?: string;
rules: Record<string, unknown>;
configs?: Record<string, unknown>;
ruleOptions?: (ctx: LintProviderContext) => unknown;
}): LintProviderParameters:
config.languages: Languages this provider supportsconfig.pluginName: ESLint plugin name (default:'codepol')config.rules: ESLint rule implementationsconfig.configs: Optional ESLint config presetsconfig.ruleOptions: Optional function to derive rule options from the policy context
Returns: A LintProvider with platform: 'eslint'
rulePluginLanguagesGet
Derives all supported languages from a plugin's lint providers and tree-check provider.
function rulePluginLanguagesGet(plugin: CodepolPluginRule): string[]Parameters:
plugin: The rule plugin to inspect
Returns: Deduplicated array of language IDs
ESLINT_PLUGIN_NAME_DEFAULT
Default ESLint plugin name used when none is specified in provider config.
const ESLINT_PLUGIN_NAME_DEFAULT = 'codepol';Workspace Package Discovery
workspacePackageMapDiscover
Discovers all workspace packages in a monorepo and maps each package name to the absolute path of its source entry-point file. Supports pnpm, npm, and yarn workspace layouts.
function workspacePackageMapDiscover(rootDir: string): Map<string, string>Parameters:
rootDir: Absolute path to the monorepo root directory
Returns: Map of package name to absolute source entry-point file path
Discovery order:
pnpm-workspace.yaml(pnpm)- Root
package.jsonworkspacesfield (npm / yarn)
Entry-point resolution per package:
- Derive source path from
exports["."]ormain(dist/→src/,.js→.ts) - Fallback to
src/index.tsnext to the package'spackage.json
Example:
import { workspacePackageMapDiscover } from '@codepol/core';
const packages = workspacePackageMapDiscover('/path/to/monorepo');
for (const [name, entryPoint] of packages) {
console.log(`${name} → ${entryPoint}`);
}
// @codepol/core → /path/to/monorepo/packages/core/src/index.ts
// @codepol/plugin → /path/to/monorepo/packages/plugin/src/index.tsThis is used internally by the semantic index for monorepo-aware module resolution (resolving bare specifiers like import { foo } from '@codepol/core' to their source files).
Result Utilities
Codepol uses a lightweight Result type instead of thrown exceptions for operations that can fail. This makes error handling explicit at the type level.
Result
Discriminated union for success (Ok) or error (Err).
type Result<T, E> = { Ok: T; Err?: never } | { Err: E; Ok?: never };Ok / Err
Constructors for creating Result values.
function Ok<T>(value: T): Result<T, never>
function Err<E>(error: E): Result<never, E>Example:
import { Ok, Err, type Result } from '@codepol/core';
function divide(a: number, b: number): Result<number, string> {
if (b === 0) return Err('Division by zero');
return Ok(a / b);
}isOk / isErr
Type guards for narrowing Result values.
function isOk<T, E>(result: Result<T, E>): result is { Ok: T; Err?: never }
function isErr<T, E>(result: Result<T, E>): result is { Err: E; Ok?: never }Example:
import { isOk, isErr } from '@codepol/core';
const result = await policyViolationsGetFromDir(config, cwd);
if (isOk(result)) {
console.log(`${result.Ok.length} violations`);
}
if (isErr(result)) {
console.error(result.Err);
}resultFrom / resFrom
Wraps a synchronous function, converting thrown exceptions to Result.Err. resFrom is an alias.
function resultFrom<T, E>(fn: () => T): Result<T, E>
const resFrom = resultFrom;Example:
import { resultFrom } from '@codepol/core';
const result = resultFrom(() => JSON.parse(rawJson));resFromAsync
Async version of resultFrom. Wraps an async function, converting rejections to Result.Err.
async function resFromAsync<T, E>(
fn: () => Promise<T>
): Promise<Result<T, E>>Example:
import { resFromAsync } from '@codepol/core';
const result = await resFromAsync(() => fetch(url).then(r => r.json()));Semantic Index (Cross-File Analysis)
The semantic index provides project-wide cross-file analysis. It extracts symbols, scopes, and relations from source files using Tree-sitter, resolves cross-file imports/exports, and exposes a read-only query API for plugins.
Core Types
SymbolId / ScopeId / FlowNodeId
Stable string identifiers for index entities. IDs are deterministic and stable across re-indexing when content is unchanged.
type SymbolId = string;
type ScopeId = string;
type FlowNodeId = string;ByteRange
Byte range within a file. Uses byte offsets (not line/column) for precision and performance.
type ByteRange = {
start: number; // Inclusive
end: number; // Exclusive
};SymbolKind
Language-agnostic symbol kinds. Adapters map language-specific node types to these canonical kinds.
type SymbolKind =
| 'module'
| 'namespace'
| 'class'
| 'interface'
| 'type'
| 'function'
| 'method'
| 'variable'
| 'const'
| 'field'
| 'parameter'
| 'enum'
| 'enumMember';ScopeKind
Language-agnostic scope kinds. Scopes form a tree for name resolution and visibility.
type ScopeKind =
| 'file'
| 'module'
| 'type'
| 'function'
| 'block'
| 'class'
| 'namespace';SymbolFlags
Symbol attribute flags as a bitset. Combine with bitwise OR.
import { SymbolFlags } from '@codepol/core';
SymbolFlags.None // 0
SymbolFlags.Exported // 1
SymbolFlags.Async // 2
SymbolFlags.Generator // 4
SymbolFlags.Static // 8
SymbolFlags.Abstract // 16
SymbolFlags.Readonly // 32
SymbolFlags.Optional // 64
SymbolFlags.Private // 128
SymbolFlags.Protected // 256
SymbolFlags.Public // 512Example:
import { SymbolFlags } from '@codepol/core';
const isExported = (symbol.flags & SymbolFlags.Exported) !== 0;
const isAsyncExport = (symbol.flags & (SymbolFlags.Exported | SymbolFlags.Async)) ===
(SymbolFlags.Exported | SymbolFlags.Async);SymbolRecord
A symbol (declaration) in the semantic index.
type SymbolRecord = {
id: SymbolId;
kind: SymbolKind;
name: string; // Declared name (local, not qualified)
file: string; // Absolute file path
byteRange: ByteRange;
scopeId: ScopeId; // Scope that contains this symbol
qualName: string; // Qualified name for disambiguation
flags: number; // SymbolFlags bitset
};ScopeRecord
A scope (lexical/semantic boundary). Scopes form a tree via the parent field.
type ScopeRecord = {
id: ScopeId;
kind: ScopeKind;
file: string;
byteRange: ByteRange;
parent?: ScopeId; // undefined for file scope
};SymbolFilter
Filter options for symbol queries.
type SymbolFilter = {
file?: string;
kind?: SymbolKind;
name?: string;
scopeId?: ScopeId;
};IndexCapabilities
Declares what capabilities the index supports. Plugins should check this before attempting advanced queries.
type IndexCapabilities = {
crossFileResolution: boolean;
callGraph: 'none' | 'heuristic' | 'precise';
controlFlowGraph: boolean;
supportedLanguages: string[];
};Relation Types
Relations are append-only facts extracted by language adapters. RelationRecord is a union of all 8 relation types.
DefinesRelation
A scope declares a symbol.
type DefinesRelation = {
kind: 'Defines';
scopeId: ScopeId;
symbolId: SymbolId;
};ContainsRelation
A scope contains a child scope.
type ContainsRelation = {
kind: 'Contains';
scopeId: ScopeId;
childScopeId: ScopeId;
};ReferencesRelation
An identifier refers to a symbol. resolvedSymbolId is populated during file-local resolution; undefined if resolution failed.
type ReferencesRelation = {
kind: 'References';
scopeId: ScopeId;
name: string;
byteRange: ByteRange;
resolvedSymbolId?: SymbolId;
};ImportsRelation
A scope imports from a module specifier. resolvedModulePath is populated during cross-file resolution.
type ImportsRelation = {
kind: 'Imports';
scopeId: ScopeId;
spec: string;
byteRange: ByteRange;
resolvedModulePath?: string;
};CallsRelation
A call expression within a scope. resolvedSymbolId is populated during resolution.
type CallsRelation = {
kind: 'Calls';
scopeId: ScopeId;
calleeName: string;
byteRange: ByteRange;
resolvedSymbolId?: SymbolId;
};ImportBindingRelation
Links an imported name to its source module. This is the key relation for cross-file symbol resolution.
type ImportBindingRelation = {
kind: 'ImportBinding';
localSymbolId: SymbolId;
importedName: string;
moduleSpec: string;
resolvedModulePath?: string;
resolvedExportId?: SymbolId;
isDefault: boolean;
isNamespace: boolean;
byteRange: ByteRange;
};ExportsRelation
Marks a symbol as exported from its module.
type ExportsRelation = {
kind: 'Exports';
symbolId: SymbolId;
exportedName: string;
isDefault: boolean;
sourceModule?: string; // For re-exports
sourceName?: string; // For re-exports
byteRange: ByteRange;
};TypeRelation
Captures extends/implements relationships between classes and interfaces.
type TypeRelation = {
kind: 'TypeRelation';
symbolId: SymbolId;
targetName: string;
relationKind: 'extends' | 'implements';
byteRange: ByteRange;
resolvedTargetId?: SymbolId;
};Control Flow Graph Types
FlowNodeKind
Language-agnostic control flow node kinds.
type FlowNodeKind =
| 'entry' // Function entry point (exactly one per CFG)
| 'exit' // Function exit point (exactly one per CFG)
| 'statement' // Basic statement
| 'branch' // Decision point (if, loop condition)
| 'merge' // Where branches rejoin
| 'loop' // Loop header (back-edge target)
| 'return'
| 'throw';FlowNode
A node in a control flow graph.
type FlowNode = {
id: FlowNodeId;
kind: FlowNodeKind;
byteRange?: ByteRange; // undefined for synthetic entry/exit
label?: string;
};FlowEdge
An edge representing a possible transition between control flow points.
type FlowEdge = {
from: FlowNodeId;
to: FlowNodeId;
label?: 'true' | 'false' | 'loop-back' | 'unconditional'
| 'break' | 'continue' | 'case' | 'default'
| 'exception' | 'finally';
};FlowGraph
A control flow graph for a single function/method scope. Contains exactly one entry node and one exit node.
type FlowGraph = {
scopeId: ScopeId;
nodes: FlowNode[];
edges: FlowEdge[];
};Module Resolution
ModuleResolveOptions
Options for module resolution.
type ModuleResolveOptions = {
baseDir: string;
extensions: string[];
pathAliases?: Record<string, string[]>;
indexedFiles?: Set<string>;
workspacePackages?: Map<string, string>;
};moduleResolve
Resolves a module specifier to an absolute file path.
function moduleResolve(
specifier: string,
fromFile: string,
options: ModuleResolveOptions
): string | undefinedParameters:
specifier: The import specifier (e.g.,'./utils','lodash')fromFile: Absolute path of the importing fileoptions: Resolution options
Returns: Absolute file path if resolved, undefined for external packages or unresolvable specifiers
Resolution strategy:
- Workspace packages: check
workspacePackagesmap first - External packages (
@org/pkg,lodash): returnundefined - Relative imports: resolve relative to importing file, try exact path, then extensions, then directory index
- Path aliases: expand alias and resolve
- Absolute paths: try extensions and directory index
isRelativeImport
function isRelativeImport(specifier: string): booleanReturns true if the specifier starts with ./ or ../.
isExternalPackage
function isExternalPackage(specifier: string): booleanReturns true if the specifier is likely an external package (not relative, not absolute).
DEFAULT_EXTENSIONS
Default extensions tried when resolving modules.
const DEFAULT_EXTENSIONS = [
'.ts', '.tsx', '.js', '.jsx',
'.mts', '.cts', '.mjs', '.cjs',
];Query API
ProjectIndex
Read-only query interface for the semantic index. This is the stable API exposed to plugins for cross-file analysis. All queries are total (return empty arrays, never null).
type ProjectIndex = {
// Symbol Queries
symbolsGet(filter?: SymbolFilter): SymbolRecord[];
symbolGet(id: SymbolId): SymbolRecord | undefined;
symbolsInFileGet(file: string): SymbolRecord[];
symbolsGetByName(name: string): SymbolRecord[];
// Reference Queries
referencesGet(symbolId: SymbolId): ReferencesRelation[];
referencesInFileGet(file: string): ReferencesRelation[];
// Call Graph Queries (heuristic)
callersGet(symbolId: SymbolId): SymbolId[];
calleesGet(symbolId: SymbolId): SymbolId[];
// Scope Queries
scopeGet(id: ScopeId): ScopeRecord | undefined;
scopesInFileGet(file: string): ScopeRecord[];
symbolsInScopeGet(scopeId: ScopeId): SymbolRecord[];
// Import/Export Queries
importsGet(file: string): ImportsRelation[];
importBindingsGet(file: string): ImportBindingRelation[];
importBindingGetForSymbol(symbolId: SymbolId): ImportBindingRelation | undefined;
exportedSymbolsGet(filter?: { file?: string; name?: string }): SymbolRecord[];
exportersGet(symbolName: string): SymbolRecord[];
fileExportsGet(file: string): ExportsRelation[];
exportLocationsGet(symbolId: SymbolId): { file: string; exportedName: string }[];
importResolve(fromFile: string, specifier: string, name: string): SymbolId | undefined;
// Type Relation Queries
typeRelationsGet(symbolId: SymbolId): TypeRelation[];
subTypesGet(symbolId: SymbolId): TypeRelation[];
typeRelationsInFileGet(file: string): TypeRelation[];
// Module Graph Queries
moduleImportersGet(file: string): string[];
moduleImporteesGet(file: string): string[];
moduleDependencyOrderGet(): string[];
moduleCyclesGet(): string[][];
moduleEntryPointsGet(): string[];
// Control Flow Graph Queries
cfgGet(scopeId: ScopeId): FlowGraph | undefined;
cyclomaticComplexityGet(symbolId: SymbolId): number | undefined;
// Metadata
readonly capabilities: IndexCapabilities;
filesGet(): string[];
statsGet(): { files: number; symbols: number; scopes: number; relations: number };
};Example:
import {
parserInit,
langAdd,
projectIndexBuild,
SymbolFlags,
} from '@codepol/core';
langAdd({ langId: 'typescript', fileExtensions: ['.ts'] });
langAdd({ langId: 'tsx', fileExtensions: ['.tsx'] });
await parserInit();
const { index, stats } = await projectIndexBuild({
files: ['/src/index.ts', '/src/utils.ts'],
dir: '/project',
});
console.log(`Indexed ${stats.filesIndexed} files`);
// Find all exported functions
const exported = index.symbolsGet({ kind: 'function' })
.filter(s => (s.flags & SymbolFlags.Exported) !== 0);
// Check for circular dependencies
const cycles = index.moduleCyclesGet();
if (cycles.length > 0) {
console.warn(`Found ${cycles.length} circular dependency cycle(s)`);
}
// Get cyclomatic complexity
const fn = index.symbolsGet({ name: 'processData', kind: 'function' })[0];
if (fn) {
const complexity = index.cyclomaticComplexityGet(fn.id);
console.log(`Complexity: ${complexity}`);
}projectIndexCreate
Creates a ProjectIndex from an IndexStore. This wraps the store in a read-only query interface.
function projectIndexCreate(
store: IndexStore,
capabilities: IndexCapabilities
): ProjectIndexParameters:
store: TheIndexStorecontaining indexed datacapabilities: Capabilities descriptor for the index
Returns: A ProjectIndex instance
Module Graph
ModuleGraph
Module-level dependency graph. All file paths are absolute, matching the paths in IndexStore.
type ModuleGraph = {
moduleGraphImportersGet(file: string): string[];
moduleGraphImporteesGet(file: string): string[];
moduleGraphDependencyOrderGet(): string[];
moduleGraphCyclesGet(): string[][];
moduleGraphEntryPointsGet(): string[];
};Methods:
moduleGraphImportersGet(file)-- files that import the given file (reverse edges)moduleGraphImporteesGet(file)-- files that the given file imports (forward edges)moduleGraphDependencyOrderGet()-- topological sort (dependencies first)moduleGraphCyclesGet()-- circular dependency cycles (Tarjan's SCC algorithm)moduleGraphEntryPointsGet()-- files with no importers (root files), sorted alphabetically
moduleGraphBuild
Builds a ModuleGraph from an IndexStore.
function moduleGraphBuild(store: IndexStore): ModuleGraphParameters:
store: TheIndexStorecontaining indexed import/export data
Returns: A ModuleGraph instance
Index Builder
IndexBuildOptions
Options for building a project index.
type IndexBuildOptions = {
files: string[];
dir: string;
languages?: string[];
store?: IndexStore;
crossFileResolution?: boolean;
pathAliases?: Record<string, string[]>;
workspacePackages?: Map<string, string>;
};Fields:
files: Files to index (absolute paths)dir: Working directory for relative paths and module resolutionlanguages: Filter to specific languages (optional, indexes all supported languages if omitted)store: Existing index store to update (creates new if not provided)crossFileResolution: Enable cross-file symbol resolution (default:true)pathAliases: Path aliases from tsconfig (e.g.,{ "@/*": ["src/*"] })workspacePackages: Workspace package name to source entry file map
IndexBuildResult
Result of building a project index.
type IndexBuildResult = {
index: ProjectIndex;
stats: {
filesIndexed: number;
filesSkipped: number;
errors: string[];
};
};projectIndexBuild
Builds a project index from a list of files (async version).
async function projectIndexBuild(
options: IndexBuildOptions
): Promise<IndexBuildResult>projectIndexBuildSync
Builds a project index from a list of files (sync version). Use this in ESLint rules where async is not supported.
function projectIndexBuildSync(
options: IndexBuildOptions
): IndexBuildResultprojectIndexUpdate
Incrementally updates the index for specific files. Only re-indexes files whose content has changed.
async function projectIndexUpdate(
store: IndexStore,
files: string[]
): Promise<{ updated: number; skipped: number; errors: string[] }>Parameters:
store: TheIndexStoreto updatefiles: Absolute file paths to update
Returns: Counts of updated/skipped files and any errors
projectIndexUpdateFileSync
Updates a single file in the index (synchronous).
function projectIndexUpdateFileSync(
store: IndexStore,
file: string
): booleanReturns: true if the file was re-indexed (content changed), false if unchanged
projectIndexUpdateFileFromSource
Updates a single file using provided source content instead of reading from disk. Important when the in-memory source (e.g., from ESLint) may differ from the on-disk version.
function projectIndexUpdateFileFromSource(
store: IndexStore,
file: string,
source: string
): booleanReturns: true if the file was re-indexed (content changed), false if unchanged
projectIndexRemoveFiles
Removes files from the index.
function projectIndexRemoveFiles(
store: IndexStore,
files: string[]
): voidcrossFileResolveForFile
Re-resolves cross-file imports and exports for a single file. Call this after updating a file to refresh its import binding resolutions.
function crossFileResolveForFile(
store: IndexStore,
file: string,
resolveOptions: ModuleResolveOptions
): voidParameters:
store: TheIndexStorefile: The file that was updatedresolveOptions: Module resolution options
adapterRegister
Registers a language adapter factory for indexing. Built-in adapters for TypeScript, TSX, and Python are registered automatically.
function adapterRegister(
languageId: string,
factory: (language: Language) => IndexAdapter
): voidParameters:
languageId: Language identifier (e.g.,'typescript','python')factory: Function that creates anIndexAdapterfrom a Tree-sitterLanguage
Index Store (Advanced Use)
The IndexStore is the mutable backing store for the semantic index. Most users should use the builder functions (projectIndexBuild, etc.) and query through ProjectIndex. Direct store access is for advanced use cases like custom adapters or incremental tooling.
FileIndexDelta
Result of indexing a single file. Produced by language adapters, consumed by IndexStore.
type FileIndexDelta = {
file: string;
revision: string;
symbols: SymbolRecord[];
scopes: ScopeRecord[];
relations: RelationRecord[];
cfgs?: FlowGraph[];
};IndexStore / indexStoreNew
In-memory store with secondary indexes for efficient lookups.
class IndexStore {
filePut(delta: FileIndexDelta): void;
fileRemove(file: string): void;
clear(): void;
symbolGet(id: SymbolId): SymbolRecord | undefined;
symbolsGet(filter?: SymbolFilter): SymbolRecord[];
scopeGet(id: ScopeId): ScopeRecord | undefined;
scopesInFileGet(file: string): ScopeRecord[];
symbolsInScopeGet(scopeId: ScopeId): SymbolRecord[];
referencesGet(symbolId: SymbolId): ReferencesRelation[];
referencesInFileGet(file: string): ReferencesRelation[];
callsInScopeGet(scopeId: ScopeId): CallsRelation[];
callsGet(): CallsRelation[];
importsInFileGet(file: string): ImportsRelation[];
importBindingsInFileGet(file: string): ImportBindingRelation[];
importBindingForSymbolGet(symbolId: SymbolId): ImportBindingRelation | undefined;
importBindingsGet(): ImportBindingRelation[];
exportsInFileGet(file: string): ExportsRelation[];
exportsGet(): ExportsRelation[];
exportMapBuild(): Map<string, Map<string, SymbolId>>;
typeRelationsForSymbolGet(symbolId: SymbolId): TypeRelation[];
typeRelationsInFileGet(file: string): TypeRelation[];
relationUpdate<R extends RelationRecord>(oldRelation: R, newRelation: R): void;
fileHasRevision(file: string, revision: string): boolean;
filesGet(): string[];
cfgGet(scopeId: string): FlowGraph | undefined;
statsGet(): { files: number; symbols: number; scopes: number; relations: number };
}
function indexStoreNew(): IndexStoreExample:
import { indexStoreNew, projectIndexCreate } from '@codepol/core';
const store = indexStoreNew();
// Use store with builder functions
const { index } = await projectIndexBuild({
files: myFiles,
dir: projectRoot,
store,
});
// Later, incrementally update
await projectIndexUpdate(store, changedFiles);
// Rebuild query interface after updates
const updatedIndex = projectIndexCreate(store, index.capabilities);@codepol/plugin-eslint
eslintPluginCreate
import { eslintPluginCreate } from '@codepol/plugin-eslint';
import pluginRules from '@codepol/plugin';
const plugin = eslintPluginCreate(pluginRules);
// plugin.rules['require-logger-enter-exit']The ESLint plugin is a thin adapter that aggregates rules from capability plugins like @codepol/plugin.
eslintAdapter
Converts a TreeCheckProvider into an ESLint rule module. This enables tree-sitter based checks to run within ESLint's infrastructure without duplicating the check logic.
import { eslintAdapter } from '@codepol/plugin-eslint';
const eslintAdapter: TreeCheckLintAdapter<TSESLint.RuleModule<string, unknown[]>>Properties:
platform:'eslint'adapt(provider, options?): Converts aCodepolPluginRuleto an ESLint rule
Example:
import { eslintAdapter } from '@codepol/plugin-eslint';
import { loggerEnterExitRule } from '@codepol/plugin';
// Convert tree-check provider to ESLint rule
const eslintRule = eslintAdapter.adapt(loggerEnterExitRule, {
ruleName: 'require-logger-enter-exit',
});
// Use in ESLint flat config
export default [
{
plugins: {
codepol: { rules: { 'require-logger-enter-exit': eslintRule } },
},
rules: {
'codepol/require-logger-enter-exit': 'error',
},
},
];eslintAdapterInit
Pre-initializes a TreeCheckProvider for use with ESLint. Call this before running ESLint to ensure async initialization completes (e.g., loading Tree-sitter WASM parsers).
async function eslintAdapterInit(
provider: CodepolPluginRule,
policy: PolicyFile,
cwd: string
): Promise<void>Parameters:
provider: TheCodepolPluginRuleto initializepolicy: The loaded policy filecwd: Current working directory
Example:
import { eslintAdapterInit, eslintAdapter } from '@codepol/plugin-eslint';
import { configGet, parserInit, langAdd } from '@codepol/core';
import { loggerEnterExitRule } from '@codepol/plugin';
// Initialize tree-sitter languages
langAdd({ langId: 'typescript', fileExtensions: ['.ts'] });
await parserInit();
// Initialize the provider
const { config } = await configGet();
await eslintAdapterInit(loggerEnterExitRule, config, process.cwd());
// Now the adapted rule will work without async init warnings
const rule = eslintAdapter.adapt(loggerEnterExitRule);policyCacheClear / providerInitStateClear
Utility functions for clearing cached state (useful for testing).
import { policyCacheClear, providerInitStateClear } from '@codepol/plugin-eslint';
// Clear cached policy files
policyCacheClear();
// Clear provider initialization state
providerInitStateClear();Note: The old names clearPolicyCache and clearProviderInitState are still available but deprecated.
@codepol/plugin
Rule Plugins
import pluginRules, { loggerEnterExitRule, loggerLintProvider } from '@codepol/plugin';
// loggerEnterExitRule.id === '@codepol/plugin/require-logger-enter-exit'
// loggerEnterExitRule.capabilities.lintProviders contains loggerLintProvider
// loggerLintProvider.platform === 'eslint'
// loggerLintProvider.languages === ['typescript', 'tsx']
// pluginRules (array for convenience)Use (lintProvider.config as EslintProviderConfig).ruleOptions?.({ policy, configPath, cwd, ruleId, ruleArgs }) to get the ESLint rule options. Severity is defined in codepol.toml per rule.
policyCacheClear
Clears the policy file cache used by the logger plugin.
import { policyCacheClear } from '@codepol/plugin';
policyCacheClear();Config file example:
[[plugins]]
id = "@codepol/plugin"
source = { kind = "builtin" }
[targets.typescript-src]
language = "typescript"
files = ["src/**/*.ts"]
[[rules]]
ruleId = "@codepol/plugin/require-logger-enter-exit"
targets = ["typescript-src"]
[rules.args.logger]
identifier = "logger"
enterMethod = "enter"
exitMethod = "exit"
import = { module = "@org/logger", named = "logger" }@codepol/plugin-esbuild
esbuildPluginCreate
Creates an esbuild plugin for policy enforcement.
function esbuildPluginCreate(options?: PolicyPluginOptions): PluginParameters:
type PolicyPluginOptions = {
configPath?: string; // Path to config file (auto-discovered if not specified)
eslintConfigPath?: string; // Path to ESLint config (uses config value or auto-detects)
fix?: boolean; // Default: false
cwd?: string; // Default: esbuild's absWorkingDir or cwd
};Returns: esbuild Plugin
Example (zero-config):
import { build } from 'esbuild';
import { esbuildPluginCreate } from '@codepol/plugin-esbuild';
await build({
entryPoints: ['src/index.ts'],
bundle: true,
outdir: 'dist',
plugins: [
// Auto-discovers codepol.toml from project root
esbuildPluginCreate(),
],
});Example (with explicit config):
plugins: [
esbuildPluginCreate({
configPath: './config/codepol.toml',
fix: true,
}),
]Complete Example
Custom policy checker script:
// scripts/check-policy.ts
import {
parserInit,
configGet,
configGetFromPath,
policyViolationsGetFromDir,
policyViolationsGetOutputPretty,
type PolicyViolation,
} from '@codepol/core';
async function main() {
const configPath = process.argv[2];
// Load config: explicit path or auto-discover
const { config } = configPath
? await configGetFromPath(configPath)
: await configGet();
await parserInit();
console.log(`Checking policy: ${config.rules.map(r => r.id).join(', ')}`);
const violationsResult = await policyViolationsGetFromDir(
config,
process.cwd()
);
if ('Err' in violationsResult) {
console.error(violationsResult.Err);
process.exit(1);
}
if (violationsResult.Ok.length === 0) {
console.log('✔ All checks passed!');
return;
}
console.log(`\n✖ Found ${violationsResult.Ok.length} violation(s):\n`);
console.log(
policyViolationsGetOutputPretty(violationsResult.Ok, process.cwd())
);
// Group by rule
const byRule = violationsResult.Ok.reduce((acc, v) => {
acc[v.ruleId] = acc[v.ruleId] || [];
acc[v.ruleId].push(v);
return acc;
}, {} as Record<string, PolicyViolation[]>);
console.log('\nSummary:');
for (const [ruleId, ruleViolations] of Object.entries(byRule)) {
console.log(` ${ruleId}: ${ruleViolations.length} violation(s)`);
}
process.exit(1);
}
main().catch(console.error);Run with:
# Auto-discovers codepol.toml
npx ts-node scripts/check-policy.ts
# Or with explicit config path
npx ts-node scripts/check-policy.ts ./config/codepol.toml