{"id":51412995,"url":"https://github.com/mateffy/gesetz","last_synced_at":"2026-07-04T16:02:49.615Z","repository":{"id":367676871,"uuid":"1281874325","full_name":"mateffy/gesetz","owner":"mateffy","description":"Gesetz (German for \"laws\") — a unified code-quality gate that lets you write your own project rules as easily as writing a config file.","archived":false,"fork":false,"pushed_at":"2026-06-27T03:36:02.000Z","size":348,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-27T05:06:30.084Z","etag":null,"topics":["ci","coding-agents","linter","quality-assurance","testing"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/gesetz","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mateffy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-27T03:06:54.000Z","updated_at":"2026-06-27T03:36:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mateffy/gesetz","commit_stats":null,"previous_names":["mateffy/gesetz"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/mateffy/gesetz","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mateffy%2Fgesetz","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mateffy%2Fgesetz/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mateffy%2Fgesetz/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mateffy%2Fgesetz/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mateffy","download_url":"https://codeload.github.com/mateffy/gesetz/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mateffy%2Fgesetz/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35127443,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ci","coding-agents","linter","quality-assurance","testing"],"created_at":"2026-07-04T16:02:48.844Z","updated_at":"2026-07-04T16:02:49.593Z","avatar_url":"https://github.com/mateffy.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\u003cdiv\u003e\n  \u003cimg src=\"./resources/icon.svg\" align=\"left\" width=\"175\"\u003e\n\u003c/div\u003e\n\n# `gesetz`\n\n**Gesetz** [*ɡəˈzɛts, German for \"law\"*] is a unified quality assurance gate that lets you write your own code-quality and architecture rules as easily as writing a config file — and runs them alongside your existing linters and test runners in a single, scored report.\n\n\u003cbr\u003e\n\n## Why Gesetz?\n\nEvery codebase has conventions that no generic linter knows about:\n\n- *\"Every module in `src/` must have a `README.md`\"*\n- *\"No file should exceed 400 lines\"*\n- *\"No one should import from `src/legacy/` — we're migrating away\"*\n- *\"Every API endpoint file needs a sibling `.test.ts`\"*\n- *\"Console logs left in production code break our log pipeline\"*\n- *\"Feature A must not import internals from Feature B\"*\n\nESLint, PHPStan, and Vitest are excellent at what they do. But they don't know *your* architecture. Gesetz bridges that gap: **you write project-specific rules in plain TypeScript, and Gesetz runs them alongside your existing tools in one unified `Violation` format, one category score, one CLI.**\n\n**Gesetz does not replace your linters.** It wraps them. You still run ESLint, Vitest, PHPStan — but their output and your custom rules all feed into the same report. Because the rule engine is polyglot, the same `gesetz check` covers your TypeScript frontend, your PHP backend, and whatever else lives in the repo.\n\n| Category | What it measures |\n|---|---|\n| **strictness** | Type discipline: `any`, double casts, missing return types, enums, default exports |\n| **structure** | Code shape: file/function size, nesting, magic numbers, empty catch blocks |\n| **organization** | Monorepo health: cycles, layer violations, import discipline, file pairing |\n| **cleanup** | Dead code, AI residue: console logs, trivial comments, debugging files |\n| **security** | Secrets, SQL injection, unsafe innerHTML, hardcoded tokens |\n\nCategories are extensible — `category` is just a string, so you can define your own (e.g. `category: 'api-conventions'` or `category: 'react'`).\n\nThe goal is simple: **one command, one score, one decision.** Pass or fail.\n\n## Quick start\n\n### 1. Install Gesetz and the adapters you need\n\n```bash\n# Core + CLI (lightweight — no heavy deps)\nbun add -d gesetz\n\n# Language adapters for your stack (install only what you use)\nbun add -d @gesetz/typescript      # TS/JS: oxc-parser + ast-grep\nbun add -d @gesetz/php            # PHP: @ast-grep/lang-php (optional peer dep)\n\n# Tool adapters for the linters/test runners you already use\nbun add -d @gesetz/eslint @gesetz/vitest\n```\n\n### 2. Initialize a config\n\n```bash\ngesetz init\n```\n\nThis creates a `gesetz.config.ts` at your project root. In a TTY it runs an interactive wizard; in CI or agent mode it auto-detects your framework and installed tools.\n\n### 3. Run checks\n\n```bash\ngesetz check\n```\n\nOutput (TTY):\n\n```\n┌─────────────┬───────┬────────┬─────────┬─────────┐\n│ Category    │ Score │ Errors │ Warns   │ Infos   │\n├─────────────┼───────┼────────┼─────────┼─────────┤\n│ strictness  │ 9.0   │ 0      │ 2       │ 0       │\n│ structure   │ 7.5   │ 1      │ 5       │ 0       │\n│ cleanup     │ 10.0  │ 0      │ 0       │ 0       │\n└─────────────┴───────┴────────┴─────────┴─────────┘\n✅ All categories above threshold\n```\n\n### 4. Agent / CI mode\n\n```bash\n# JSON output for agents\ngesetz check --format=json\n\n# GitHub Actions annotations\ngesetz check --format=ci\n\n# Only changed files since main\ngesetz check --since main\n```\n\n---\n\n## Write your own project rules in 5 lines\n\nA rule is just a glob + a check. Enforce any convention your team agrees on:\n\n```ts\n// rules/coverage.ts — every module needs a test\nimport { select, requireSibling } from 'gesetz';\n\nexport const everyFileNeedsTest = select('src/**/*.ts')\n  .exclude('**/*.test.ts', '**/index.ts')\n  .label('Every source file needs a test')\n  .check(requireSibling('.test.ts'));\n```\n\n```ts\n// rules/quality.ts — no god files, no hardcoded secrets\nimport { select, noGodFile, noHardcodedSecret, noDebugLogging } from 'gesetz';\n\nexport const noGiantFiles = select('src/**/*.{ts,tsx,php,py}')\n  .label('Files should not exceed 400 lines')\n  .check(noGodFile({ maxLines: 400 }));\n\nexport const noSecrets = select('src/**/*')\n  .label('No hardcoded secrets')\n  .check(noHardcodedSecret());\n\n// noDebugLogging is extension-aware: flags console.* in TS, var_dump/dd in PHP,\n// print in Python, fmt.Println in Go, println! in Rust, puts in Ruby — one rule.\nexport const noDebug = select('src/**/*')\n  .label('No debug logging left in code')\n  .check(noDebugLogging());\n```\n\n```ts\n// rules/migration.ts — ban imports from a deprecated module\nimport { select, noImportFrom } from 'gesetz';\n\nexport const noLegacyImports = select('src/**/*.{ts,tsx}')\n  .label('Do not import from the legacy module')\n  .check(\n    noImportFrom('src/legacy', {\n      message: 'This module is being phased out. Use src/lib/utils instead.',\n    }),\n  );\n```\n\nFor deeper structural checks — AST-level call detection, naming conventions, export pairs, call-shape validation — add `@gesetz/typescript` and register its backend:\n\n```ts\n// rules/architecture.ts — feature boundaries + AST checks\nimport { select, defineArchitecture } from 'gesetz';\nimport {\n  typescriptSyntaxBackend,\n  noFunctionCalls,\n  requireOptionsObject,\n  noTypedAny,\n} from '@gesetz/typescript';\n\n// Feature A must not import internals from Feature B\nconst arch = defineArchitecture({\n  layers: [\n    { name: 'features', pattern: 'src/features/**', canImportFrom: ['shared'] },\n    { name: 'shared',   pattern: 'src/shared/**',   canImportFrom: [] },\n  ],\n});\n\nexport const featureIsolation = select('src/features/**/*.{ts,tsx}')\n  .label('Features must not call fetch directly')\n  .check(noFunctionCalls(['fetch']));\n\nexport const queryOptionsShape = select('src/api/**/*.ts')\n  .label('queryOptions() must define queryKey and queryFn')\n  .check(requireOptionsObject('queryOptions', { requiredKeys: ['queryKey', 'queryFn'] }));\n\nexport const noAny = select('src/**/*.ts').check(noTypedAny());\n\nexport default [featureIsolation, queryOptionsShape, noAny, ...arch];\n```\n\nWire everything into your config:\n\n```ts\n// gesetz.config.ts\nimport { defineConfig } from 'gesetz';\nimport { typescriptSyntaxBackend } from '@gesetz/typescript';\nimport { eslint } from '@gesetz/eslint';\nimport { vitest } from '@gesetz/vitest';\nimport * as coverage from './rules/coverage';\nimport * as quality from './rules/quality';\nimport * as migration from './rules/migration';\nimport * as architecture from './rules/architecture';\n\nexport default defineConfig({\n  adapters: [typescriptSyntaxBackend],   // enables AST checks + accurate imports\n  rules: [\n    // Your custom rules\n    coverage.everyFileNeedsTest,\n    quality.noGiantFiles,\n    quality.noSecrets,\n    quality.noDebug,\n    migration.noLegacyImports,\n    ...architecture.default,              // the arch array exported above\n\n    // Your existing tools — now unified in the same report\n    eslint({ pattern: 'src/**/*.{ts,tsx}' }),\n    vitest({ pattern: 'src' }),\n  ],\n});\n```\n\n---\n\n## Core concepts\n\n### `Violation` — the universal report format\n\nEvery rule, check, and adapter produces the same shaped object. This is what makes Gesetz \"one command, one report\":\n\n```ts\ninterface Violation {\n  rule: string;               // rule ID that produced this violation\n  message: string;            // human-readable message\n  path: string;               // repository-relative file path\n  line?: number;              // 1-based line number (optional)\n  column?: number;            // 1-based column number (optional)\n  severity: 'error' | 'warn' | 'info';\n  context?: string;           // optional surrounding code snippet\n  fix?: string;               // optional suggested fix\n  source: 'core' | 'eslint' | 'phpstan' | 'oxlint' | 'custom';\n}\n```\n\n### `Check` — a single-file analysis\n\nA `Check` receives a `File` object and returns `Effect.Effect\u003cViolation[], never, …\u003e`. It never throws — errors become violations or empty arrays.\n\n```ts\nimport { Effect } from 'effect';\nimport type { Check, File, Violation } from 'gesetz';\n\n// A check that forbids urgent TODO comments\nconst noTodoComments: Check = (file) =\u003e\n  Effect.sync(() =\u003e {\n    const violations: Violation[] = [];\n    file.content.split('\\n').forEach((line, index) =\u003e {\n      if (/TODO\\s*\\(urgent\\)/i.test(line)) {\n        violations.push({\n          rule: '', source: 'custom', severity: 'error',\n          path: file.path, line: index + 1,\n          message: `Urgent TODO found: \"${line.trim()}\"`,\n        });\n      }\n    });\n    return violations;\n  });\n\nimport { select } from 'gesetz';\nexport const noUrgentTodos = select('src/**/*.ts')\n  .label('No urgent TODOs in production')\n  .category('cleanup')\n  .check(noTodoComments);\n```\n\nA `File` gives you everything you need for text-based analysis:\n\n```ts\ninterface File {\n  path: string;          // e.g. \"src/components/Foo.tsx\"\n  absolutePath: string;  // e.g. \"/repo/src/components/Foo.tsx\"\n  name: string;          // \"Foo.tsx\"\n  stem: string;          // \"Foo\"\n  ext: string;           // \".tsx\"\n  dir: string;           // \"src/components\"\n  content: string;       // full UTF-8 content (read lazily on first access)\n  size: number;          // bytes\n  mtimeMs: number;       // last modified timestamp\n}\n```\n\n### `Rule` — the top-level unit of work\n\nA `Rule` has an `id`, `description`, `category`, and a `run` Effect that returns violations. Rules are self-contained — they can spawn external processes, read files, or call `SyntaxTree` — but they never throw.\n\n**Using `select` (recommended)** — most rules are built by chaining:\n\n```ts\nimport { select, requireSibling } from 'gesetz';\n\nexport const everyComponentNeedsStory = select('src/**/*.tsx')\n  .exclude('**/*.test.tsx')\n  .label('Every component needs a Storybook story')\n  .category('organization')\n  .check(requireSibling('.stories.tsx'));\n```\n\n**Writing a raw rule** — when you need full control (running an external tool, scanning the whole project at once):\n\n```ts\nimport { Effect } from 'effect';\nimport { FileSystem, ProjectRoot } from 'gesetz';\nimport type { Rule, Violation } from 'gesetz';\n\nexport const noSecretsInEnv: Rule = {\n  id: 'no-secrets-in-env',\n  description: 'Repo .env files must not contain hardcoded secrets',\n  category: 'security',\n  run: Effect.gen(function* () {\n    const fs = yield* FileSystem;\n    const root = yield* ProjectRoot;\n    const envFiles = yield* fs.glob(['**/.env', '**/.env.local'], { cwd: root });\n    const violations: Violation[] = [];\n    for (const file of envFiles) {\n      if (/API_KEY\\s*=\\s*[\"']?[a-zA-Z0-9]{32}[\"']?/m.test(file.content)) {\n        violations.push({\n          rule: 'no-secrets-in-env', source: 'custom', severity: 'error',\n          path: file.path, message: 'Possible hardcoded API key in .env file',\n        });\n      }\n    }\n    return violations;\n  }),\n};\n```\n\n### Consuming the output\n\n**CLI** — structured JSON for agents and CI:\n\n```bash\n$ gesetz check --format=json\n# {\n#   \"byRule\":     [ { \"ruleId\": \"...\", \"violations\": [...] } ],\n#   \"byCategory\": [ { \"category\": \"cleanup\", \"score\": 9.5, \"passing\": true } ],\n#   \"passing\": true\n# }\n```\n\n**Programmatically** — import `runAll` and provide the service layers your rules need. For any rule that uses `SyntaxTree` (architecture, imports, cycles, structural checks) or `ImportResolver`, wire `SyntaxTreeLive(config.adapters)` and `ImportResolverDefault`:\n\n```ts\nimport { Effect, Layer } from 'effect';\nimport {\n  runAll, FileSystemLive, ProjectRootLive, FileFilterLive,\n  SyntaxTreeLive, ImportResolverDefault,\n} from 'gesetz';\nimport { typescriptSyntaxBackend } from '@gesetz/typescript';\n\nconst config = defineConfig({\n  adapters: [typescriptSyntaxBackend],\n  rules: [/* ... */],\n});\n\nconst program = Effect.gen(function* () {\n  const result = yield* runAll(config);\n  for (const cat of result.byCategory) {\n    console.log(`${cat.category}: ${cat.score}/10 (${cat.errors}e ${cat.warnings}w)`);\n  }\n  return result.passing;\n}).pipe(\n  Effect.provide(Layer.mergeAll(\n    FileSystemLive,\n    SyntaxTreeLive(config.adapters),\n    ImportResolverDefault,\n    ProjectRootLive('.'),\n    FileFilterLive(null),\n  )),\n);\n\nEffect.runPromise(program);\n```\n\n### `select` — the rule builder\n\n```ts\nconst rule = select('src/**/*.tsx')\n  .exclude('**/*.test.tsx', '**/*.stories.tsx')\n  .label('All components need Storybook stories')\n  .category('organization')\n  .guidance({\n    what: 'Components without a story file are invisible to design-system consumers.',\n    do: 'Create a Foo.stories.tsx next to every component.',\n    dont: 'Skip stories for \"simple\" components — they all evolve.',\n  })\n  .check(requireSibling('.stories.tsx'));\n```\n\n| Method | Purpose |\n|---|---|\n| `.exclude(...globs)` | Remove matching files |\n| `.include(...globs)` | Add more patterns |\n| `.filter(fn)` | Custom predicate on `File` |\n| `.label(string)` | Human description (auto-slugified to `rule.id`) |\n| `.category(string)` | Scoring bucket |\n| `.guidance({what,do,dont})` | Agent-facing docs for `gesetz list` / `gesetz skill` |\n| `.check(...checks)` | Apply checks to every matched file |\n| `.forEach(check)` | Sugar for a single check |\n\n### `defineConfig`\n\n```ts\nimport { defineConfig, select, noGodFile, noHardcodedSecret } from 'gesetz';\nimport { typescriptSyntaxBackend, noConsoleLog, noHardcodedStrings } from '@gesetz/typescript';\nimport { eslint } from '@gesetz/eslint';\nimport { vitest } from '@gesetz/vitest';\n\nexport default defineConfig({\n  projectRoot: '.',\n  tsConfigPath: 'tsconfig.json',\n  adapters: [typescriptSyntaxBackend],     // enables AST checks + accurate imports\n  rules: [\n    select('src/**/*.tsx').label('Components need stories').category('organization')\n      .check(requireSibling('.stories.tsx')),\n    select('src/**/*.ts').label('No console.log').category('cleanup').check(noConsoleLog()),\n    select('src/**/*.tsx').label('No hardcoded user-facing strings').category('strictness')\n      .check(noHardcodedStrings()),\n    vitest({ pattern: 'src', label: 'Unit tests' }),\n    eslint({ pattern: 'src/**/*.{ts,tsx}', label: 'ESLint' }),\n  ],\n  exemptions: [\n    { path: 'src/legacy/**', reason: 'Migration in progress', until: '2026-08-01' },\n  ],\n  thresholds: [\n    { category: 'strictness', minScore: 8 },\n    { category: 'organization', minScore: 7 },\n  ],\n});\n```\n\n---\n\n## CLI commands\n\n### `gesetz check`\n\n```bash\ngesetz check                              # full scan\ngesetz check --since HEAD~5               # only changed files\ngesetz check --since main                 # diff against a branch\ngesetz check --category strictness        # run one category\ngesetz check --format json                # JSON envelope (agents/CI)\ngesetz check --format ci                  # GitHub Actions annotations\ngesetz check --threshold 8                # override all thresholds\ngesetz check --files \"src/components/**\"  # subset of files\ngesetz check --project-root ./apps/web    # monorepo workspace\n```\n\n### `gesetz list`\n\n```bash\ngesetz list                       # all rules with guidance\ngesetz list --category strictness # filter by category\ngesetz list --format json         # JSON for agents\n```\n\n### `gesetz init`\n\n```bash\ngesetz init                       # interactive wizard\ngesetz init --preset react        # explicit preset\ngesetz init --preset laravel\ngesetz init --no-interactive      # auto-detect + non-interactive\ngesetz init --no-install          # scaffold only, no packages\ngesetz init --no-qa-script        # skip adding a package.json script\ngesetz init --force               # overwrite existing config\n```\n\n### `gesetz skill`\n\n```bash\ngesetz skill \u003e .agents/skills/gesetz/SKILL.md\n```\n\nPrints a markdown agent skill file you can pipe directly into your AI agent's skill directory.\n\n---\n\n## The check catalog\n\nChecks are grouped by **what they need to run** — the thing you actually have to know when composing a config.\n\n### A. Universal text/regex checks (core, any file, no backend)\n\nThese live in `@gesetz/core` and work on any file using text analysis or the file system. No AST, no type-checker, no language-specific parser. Fast and universal.\n\n#### File-system checks\n\n**`requireSibling(suffix, opts?)`** — checks that a sibling file with the given suffix exists. `Foo.tsx` + `.test.ts` → looks for `Foo.test.ts`.\n\n```ts\nrequireSibling('.test.ts')\nrequireSibling('.stories.tsx', { message: 'Components need a story file' })\n```\n\n**`requireChildren(requiredPaths, opts?)`** — checks that the directory containing each matched file also contains every file in the list.\n\n```ts\nrequireChildren(['types.ts', 'interface.ts'])\nrequireChildren(['README.md'], { message: (m) =\u003e `Package missing ${m}` })\n```\n\n**`forbidFile(opts?)`** — marks every matched file as a violation. Use with `select(...)` targeting files that should not exist.\n\n```ts\nselect('src/legacy/**/*').check(forbidFile({ message: 'Legacy files are being phased out' }))\n```\n\n#### Pattern checks\n\n**`noPattern(regex, opts?)`** — the file must not contain a regex match. `opts.fullFile: true` matches against the whole file as one string (default: line-by-line with line numbers).\n\n```ts\nnoPattern(/debugger;/)\nnoPattern(/TODO\\(urgent\\)/, { fullFile: true, severity: 'warn' })\n```\n\n**`requirePattern(regex, opts?)`** — the file must contain the pattern at least once.\n\n```ts\nrequirePattern(/declare\\(strict_types=1\\)/)\n```\n\n#### Structure checks\n\n**`noGodFile({ maxLines?, message? })`** — flags files exceeding a line-count threshold. Default `maxLines: 400`, severity `warn`.\n\n```ts\nnoGodFile({ maxLines: 300 })\n```\n\n**`noDeepNesting({ maxLevels?, message? })`** — flags lines whose indentation exceeds a threshold (2-space or tab). Default `maxLevels: 4`, severity `warn`, capped at 10 violations/file.\n\n```ts\nnoDeepNesting({ maxLevels: 3 })\n```\n\n**`noDebuggingResidueFiles({ extraPatterns?, message? })`** — flags files whose names look like debugging artefacts (`*_v2`, `*_backup`, `*_fixed`, `*_copy`, `*_old`, `*_new`, `*_temp`, `*_wip`, `*_draft`, `*delete_me*`).\n\n```ts\nnoDebuggingResidueFiles({ extraPatterns: [/\\.draft\\./i] })\n```\n\n**`noHardcodedSecret({ message? })`** — regex heuristic for `api_key = \"…\"`, `token: \"…\"`, `password = \"…\"`, `bearer \"…\"`, etc. Not a replacement for proper secret scanning (GitLeaks, TruffleHog). Severity `error`.\n\n```ts\nnoHardcodedSecret()\n```\n\n#### `noDebugLogging(opts?)` — polyglot debug-logging detector\n\nRegex-based, **extension-aware**. Maps file extensions to known debug function names and scans line by line. No `SyntaxTree` dependency — works on any file with a known extension.\n\n| Extension | Flags |\n|---|---|\n| `.ts` `.tsx` `.js` `.jsx` `.mjs` `.cjs` | `console.log/debug/info/warn/error/dir/table/trace` |\n| `.py` | `print`, `pprint`, `breakpoint` |\n| `.php` | `var_dump`, `print_r`, `dd`, `dump`, `debug` |\n| `.go` | `fmt.Println`, `fmt.Printf`, `log.Println`, `log.Printf` |\n| `.rs` | `println!`, `eprintln!`, `dbg!` |\n| `.rb` | `puts`, `p`, `pp` |\n\nFiles with extensions not in this map are silently skipped (not an error).\n\n```ts\nnoDebugLogging()\nnoDebugLogging({ extraNames: ['myDebugFn'], severity: 'error', message: 'No logging!' })\n```\n\n- **`opts.extraNames`** — additional function names to ban (applied to all extensions).\n- **`opts.severity`** — default `warn`.\n- **`opts.message`** — default `Remove debug logging: \u003cname\u003e`.\n\nFor more precise, AST-level banning of specific function calls, use [`noDirectCalls`](#b-syntaxtree-backed-structural-checks-core-need-adapters) (requires a backend).\n\n### B. SyntaxTree-backed structural checks (core, need `adapters`)\n\nThese live in `@gesetz/core` but require a `SyntaxBackend` to be registered for the file's extension (via `defineConfig({ adapters })`). If no backend matches the file's extension, the check silently returns no violations — it does not crash.\n\n**`noDirectCalls(names, opts?)`** — bans specific function calls by exact name (member access supported: `console.log`, `fmt.Println`). Unlike `noDebugLogging` (regex, broad), this is precise and user-specified.\n\n```ts\nnoDirectCalls(['eval', 'execSync'])\nnoDirectCalls(['console.log'], { message: (n) =\u003e `do not call ${n}!`, severity: 'warn' })\n```\n\n**`requireNamingConvention({ kinds?, pattern, message?, severity? })`** — every structural item of the given kinds must match the regex. `kinds` defaults to all (`'function'`, `'class'`, `'method'`, …).\n\n```ts\nrequireNamingConvention({ kinds: ['function', 'class'], pattern: /^[a-z][a-zA-Z0-9]*$/ })\n```\n\n**`noForbiddenNames(names | RegExp, { kinds?, message?, severity? })`** — bans specific names (string list or regex) on structural items.\n\n```ts\nnoForbiddenNames(['foo', 'bar'])\nnoForbiddenNames(/^tmp_/, { kinds: ['function'] })\n```\n\n**`requireDocstrings({ kinds?, message?, severity? })`** — structural items must have an attached docstring. Default `kinds: ['function', 'class', 'method']`.\n\n```ts\nrequireDocstrings({ kinds: ['class'] })\n```\n\n**`requireExportsMatching(pattern, minCount?, opts?)`** — the file must export at least `minCount` identifiers matching the regex. Default `minCount: 1`.\n\n```ts\nrequireExportsMatching(/Keys$/, 1)\n```\n\n**`requireRelatedExports(getRelated, opts?)`** — for every export `X`, all counterparts returned by `getRelated(X)` must also be exported. Return `null` to skip an export. *(N-ary: returns `string[]`, not one string.)*\n\n```ts\n// Every useX must have both useSuspenseX and useCachedX\nrequireRelatedExports(name =\u003e {\n  if (!name.startsWith('use')) return null;\n  const base = name.slice(3);\n  return [`useSuspense${base}`, `useCached${base}`];\n})\n```\n\n**`requireMinStructureCount(kind, minCount, opts?)`** — the file must declare at least `minCount` structural items of the given kind (counted recursively, including nested children).\n\n```ts\nrequireMinStructureCount('function', 1)\n```\n\n### C. Architecture \u0026 import graph (core; accurate with `adapters`)\n\n**`noImportFrom(module, opts?)`** — the file must not import from the given module (string exact/prefix match, or `RegExp`). Matches static imports, dynamic imports, and `require()`. Uses `SyntaxTree` for accurate specifiers when a backend is registered; falls back to a JS/TS regex otherwise.\n\n```ts\nnoImportFrom('lodash')\nnoImportFrom('@tanstack/react-query', { message: 'Use SDK hooks instead' })\nnoImportFrom(/^~\\/legacy\\//, { severity: 'warn' })\n```\n\n**`requireImportFrom(module, opts?)`** — opposite of `noImportFrom`. The file must import from the module at least once.\n\n```ts\nrequireImportFrom('vitest')\n```\n\n**`defineArchitecture(config)`** — declares monorepo layers as file-glob patterns and enforces import constraints between them. Returns `Rule[]` (one batched rule, not O(n²) per-pair rules). Import extraction uses `SyntaxTree` when a backend is registered (oxc-parser for TS/JS, `@ast-grep/lang-php` for PHP); relative specifiers are resolved to file paths via `ImportResolver`; falls back to a JS/TS regex when no backend is registered.\n\n```ts\nimport { defineConfig, defineArchitecture } from 'gesetz';\n\nconst arch = defineArchitecture({\n  layers: [\n    { name: 'entry',  pattern: 'src/cli/**',   canImportFrom: ['core', 'util'] },\n    { name: 'core',   pattern: 'src/core/**',  canImportFrom: ['util'] },\n    { name: 'util',   pattern: 'src/utils/**', canImportFrom: [] },\n  ],\n  forbidden: [\n    { from: 'util', to: 'entry', message: 'Utilities must not import from entry points' },\n  ],\n  bannedExternals: {\n    util: ['react', 'react-dom'],   // util layer may not import React\n  },\n});\n\nexport default defineConfig({ adapters: [typescriptSyntaxBackend], rules: [...arch] });\n```\n\n| Config field | Type | Description |\n|---|---|---|\n| `layers[].name` | `string` | Layer identifier used in messages and cross-references |\n| `layers[].pattern` | `string \\| string[]` | Glob(s) matching files in this layer |\n| `layers[].canImportFrom` | `string[] \\| undefined` | Layers this layer may import from. Omit to allow all. |\n| `forbidden[].from` / `.to` | `string` | Explicit denial of a source → target layer pair |\n| `forbidden[].message` | `string \\| undefined` | Custom message |\n| `bannedExternals` | `Record\u003clayer, packageName[]\u003e` | Per-layer banned npm packages (scoped supported) |\n\n**`noCycles(pattern, opts?)`** — detects circular dependencies via `SyntaxTree` (import extraction) + `ImportResolver` (path resolution) + DFS over the dependency graph. **No `dependency-cruiser`** — it's been removed. Files whose extension has no registered backend are skipped; external (non-resolvable) imports are ignored.\n\n```ts\nnoCycles('src/**/*.{ts,tsx}')\nnoCycles(['src/**/*.ts', 'apps/**/*.ts'], { label: 'No circular dependencies' })\n```\n\n### D. TypeScript / JavaScript checks (`@gesetz/typescript`)\n\nInstall: `bun add -d @gesetz/typescript`\n\nThis package exports `typescriptSyntaxBackend` (oxc-parser for imports/exports + `@ast-grep/napi` for calls/structure; handles `.ts .tsx .js .jsx .mjs .cjs`). Register it via `adapters: [typescriptSyntaxBackend]` to enable all checks below.\n\n\u003e **No `ts-morph`** — every check here is syntactic (AST traversal via ast-grep/oxc-parser). For type-checked rules like `no-floating-promises`, use `@gesetz/eslint` or `@gesetz/oxlint` with `--type-aware` (both ship the type-checked version).\n\n#### Moved from `@gesetz/core` (TS/JS-specific)\n\nThese used to live in core; they were moved because they're TypeScript/JavaScript-specific:\n\n- **`noConsoleLog({ allowWarnError?, message? })`** — bans `console.*`. `allowWarnError: true` allows `console.warn`/`console.error`.\n- **`noEmptyCatch({ message? })`** — flags empty or comment-only catch blocks. Severity `error`.\n- **`noMagicNumbers({ ignore?, message? })`** — flags unexplained numeric literals (skips `const UPPER_SNAKE = N` and the default ignore list `[0, 1, -1, 2, 100]`). Capped at 20/file.\n- **`noTrivialComment({ message? })`** — flags AI-narration comments (`// Import the module`) and decorative dividers (`// ======`). Severity `info`.\n- **`relativeImports({ message? })`** — every relative `import … from './foo'` must resolve to an existing file (`.ts`, `.tsx`, `/index.ts`, `/index.tsx`).\n\n#### AST checks (call + export + shape)\n\n**`noFunctionCalls(callNames, opts?)`** — bans direct calls to the listed function names.\n\n```ts\nnoFunctionCalls(['fetch', 'useSuspenseQuery'])\n```\n\n**`requireRelatedExports(getRelated, opts?)`** *(TS version)* — same semantics as the core one, but uses oxc-parser exports (handles re-exports, type exports).\n\n**`requireExportsMatching(pattern, minCount?, opts?)`** *(TS version)* — same as core, oxc-parser exports.\n\n**`requireOptionsObject(fnName, { argIndex?, requiredKeys })`** — every call to `fnName()` must pass an object literal at argument position `argIndex` (default 0) containing all `requiredKeys`. *(Renamed from `requireCallShape`; now supports `argIndex`.)*\n\n```ts\nrequireOptionsObject('queryOptions', { requiredKeys: ['queryKey', 'queryFn'] })\nrequireOptionsObject('useMutation', { argIndex: 1, requiredKeys: ['onMutate', 'onError', 'onSettled'] })\n```\n\n#### TypeScript strictness (new, ast-grep based)\n\n**`noTypedAny({ message? })`** — bans `any` type annotations (`: any`, `as any`, `\u003cany\u003e`).\n\n**`noAsUnknownAs({ message? })`** — bans double casts `as unknown as X` (and `as any as X`). Use a type guard instead.\n\n**`noDefaultExport({ message? })`** — bans `export default`. Named exports improve refactorability and IDE auto-import.\n\n**`noEnum({ message? })`** — bans TypeScript `enum`. Prefer union types or `as const` object maps.\n\n**`noBarrelFile({ maxReexports?, message? })`** — flags `index.{ts,tsx}` files that re-export more than `maxReexports` (default 5) modules. Barrel files harm tree-shaking.\n\n**`requireExplicitReturnType({ kinds?, ignore?, message? })`** — public functions and methods must declare an explicit return type. `kinds` default `['function', 'method']`.\n\n```ts\nrequireExplicitReturnType({ ignore: /^test[A-Z]/ })  // ignore test functions\n```\n\n#### JSX / React checks\n\n**`noLiteralJsxText({ hasLetterRegex?, message? })`** — flags JSX text nodes containing letters (`\u003cdiv\u003eHello\u003c/div\u003e`). Enforce i18n.\n\n**`noLiteralJsxProp(translatableProps, opts?)`** — flags listed JSX attributes with string-literal values.\n\n```ts\nnoLiteralJsxProp(['label', 'placeholder', 'title', 'aria-label'])\n```\n\n**`noJsxElements(elements, opts?)`** — flags JSX elements with the given tag names.\n\n```ts\nnoJsxElements(['div', 'span', 'h1', 'h2', 'p', 'ul', 'li', 'table'])\n```\n\n**`noLocalFunctionComponents({ excludeExportedNames?, message? })`** — flags non-exported function declarations that contain JSX (local helper components).\n\n#### i18n / hardcoded strings\n\n**`noHardcodedStrings(opts?)`** — the comprehensive i18n check. Flags three cases in one pass: JSX text nodes, string literals inside JSX expressions (`{\"Hello\"}`), and known text-bearing attributes (`\u003cinput placeholder=\"Search\" /\u003e`).\n\n```ts\nnoHardcodedStrings()                                          // defaults\nnoHardcodedStrings({ attributeSeverity: 'error' })            // strict on attributes\nnoHardcodedStrings({ textAttributes: ['label', 'placeholder'] })\n```\n\n- `textAttributes` — default `DEFAULT_TEXT_ATTRIBUTES` (35 common attributes: `label`, `placeholder`, `title`, `alt`, `aria-label`, …).\n- `attributeSeverity` — default `warn` (edge-case-prone; `alt=\"logo\"`).\n- `textSeverity` — default `error`.\n- `hasLetterRegex` — default `/[A-Za-zÄÖÜäöüßÀ-ÿ]/`.\n\n#### Object property\n\n**`noObjectProperty(varName, propName, opts?)`** — text-based: finds `const varName = { … }` and checks it doesn't define `propName`. Uses brace-counting for nested objects.\n\n```ts\nnoObjectProperty('meta', 'title')   // Storybook meta must not define explicit title\n```\n\n#### Directory \u0026 test quality\n\n**`requireDirectoryStructure(requiredFiles)`** — re-export of core's `requireChildren`, for discoverability.\n\n**`requireMinTestScore(scoring)`** — scores a test file by quality signals (assertion count, async tests, interaction coverage, error paths, assertion variety, trivial-assertion penalty) and returns a violation if below `minScore`. Text-based.\n\n```ts\nrequireMinTestScore({ minScore: 50 })\nrequireMinTestScore({ minScore: 60, assertionThresholds: [1, 5, 10], trivialPenalty: -30 })\n```\n\nKey params (all optional except `minScore`): `assertionThresholds` (default `[1,3,5,8]`), `assertionBonus` (`5`), `testCountThresholds` (`[2,4,6]`), `testCountBonus` (`5`), `assertionNames` (`['expect(']`), `trivialAssertions` (`['toBeTrue(','toBeTruthy(','toBeDefined(']`), `trivialPenalty` (`-20`), `asyncIndicators` (`['waitFor(','act(']`), `interactionMethods` (`['userEvent.','fireEvent.']`), `errorIndicators` (`['.toThrow(','.rejects.']`), `asyncBonus`/`interactionBonus`/`errorBonus`/`varietyBonus` (all `5`).\n\n### E. PHP checks (`@gesetz/php`)\n\nInstall: `bun add -d @gesetz/php`\n\nExports `phpSyntaxBackend` (uses `@ast-grep/lang-php`, an **optional peer dep** — run `bun pm trust @ast-grep/lang-php` once after install to place its prebuilt binary). Handles `.php`. Extracts `use` statements (including grouped `use Foo\\{A, B}` and aliased `use Foo\\Bar as Baz`), function calls, classes/methods, and docstrings.\n\n**Generic PHP checks** (text-based unless noted):\n\n- **`strictTypes({ message? })`** — file must contain `declare(strict_types=1)`.\n- **`psrNamespace({ baseNamespace, basePath, message? })`** — namespace must match PSR-4 directory structure. Files outside `basePath` are skipped.\n- **`noInlineQueries(patterns, opts?)`** — line-by-line ban on caller-provided call patterns (Laravel `DB::raw`, WordPress `$wpdb-\u003equery`, generic `PDO::query`, …).\n- **`requireTypeHints({ message? })`** — function parameters must have type hints.\n- **`requireReturnType({ message? })`** — functions must have return type declarations (`: string`, `: void`).\n- **`requireNamespace({ message? })`** — file must declare a `namespace`.\n- **`noDieOrExit({ message? })`** — bans `die()` and `exit()`.\n- **`noEval({ message? })`** — bans `eval()`.\n- **`requireFinalClasses({ message? })`** — classes must be declared `final` (skips abstract and anonymous classes).\n\n### F. Laravel presets (`@gesetz/laravel`)\n\nInstall: `bun add -d @gesetz/laravel`\n\nReady-made rules for standard Laravel projects.\n\n```ts\nimport { allRules } from '@gesetz/laravel';\nimport { defineConfig } from 'gesetz';\n\nexport default defineConfig({ rules: allRules });\n```\n\nOr pick individual rules:\n\n```ts\nimport {\n  requireStrictTypes, requirePsrNamespaces, noRawDbQueries,\n  noEnvOutsideConfig, noDebugHelpers, phpstan,\n  noDd, noFacades,\n} from '@gesetz/laravel';\n```\n\n- **`requireStrictTypes`**, **`requirePsrNamespaces`**, **`noRawDbQueries`**, **`noEnvOutsideConfig`**, **`noDebugHelpers`** — pre-built `Rule`s with Laravel path defaults.\n- **`noDd({ message?, severity? })`** — standalone `Check` banning `dd()`, `ddd()`, `dump()`, `debug()`. More precise than `noDebugHelpers` (which is a pre-built select rule); use inside `select().check()` for custom targeting.\n- **`noFacades({ facades?, message?, severity? })`** — bans Laravel Facades (`Auth::`, `DB::`, `Cache::`, …) in favor of dependency injection. `facades` defaults to a list of common facades.\n\n### G. Effect-TS checks (`@gesetz/effect-ts`)\n\nInstall: `bun add -d @gesetz/effect-ts`\n\nCatches the four most common anti-patterns AI agents introduce in Effect-TS code. All four use **ast-grep** (no `ts-morph`).\n\n**`noRunPromiseScattered({ entryPoints?, message? })`** — flags `Effect.runPromise` / `runSync` / `runFork` / `runCallback` / `runPromiseExit` outside designated entry-point files.\n\n```ts\nnoRunPromiseScattered({ entryPoints: ['src/main.ts', 'src/index.ts'] })\n```\n\n**`noThrowInEffectGen({ message? })`** — flags `throw` inside `Effect.gen()` / `Effect.fn()` / `Effect.fnUntraced()`. `throw` converts typed failures into untyped Defects — use `yield* Effect.fail(new MyError())`.\n\n**`noYieldWithoutStar({ message? })`** — flags plain `yield expr` (no `*`) inside Effect generators. `yield` returns the raw channel; `yield*` unwraps the Effect.\n\n**`noUnboundedEffectAll({ message? })`** — flags `Effect.all([...])` calls with fewer than 2 arguments (i.e. no `{ concurrency }` option). Forces explicit concurrency intent.\n\n---\n\n## Tool adapters\n\nTool adapters **wrap external CLI tools** and normalize their output into the same `Violation[]` shape. Install only the ones for tools you already use.\n\n### TypeScript / JavaScript\n\n| Package | Tool | What it does | Install |\n|---|---|---|---|\n| `@gesetz/eslint` | ESLint | Runs ESLint programmatically, maps messages to violations. Use for type-checked rules like `@typescript-eslint/no-floating-promises`. | `bun add -d @gesetz/eslint` |\n| `@gesetz/oxlint` | oxlint | Fast Rust linter — maps JSON diagnostics to violations. Use with `--type-aware` + `tsgolint` for `typescript/no-floating-promises`. | `bun add -d @gesetz/oxlint` |\n| `@gesetz/oxfmt` | oxfmt | Format check — `--list-different` | `bun add -d @gesetz/oxfmt` |\n| `@gesetz/prettier` | Prettier | Format check — `--list-different` | `bun add -d @gesetz/prettier` |\n| `@gesetz/vitest` | Vitest | Runs tests with JSON reporter, maps failures to violations | `bun add -d @gesetz/vitest` |\n| `@gesetz/bun-test` | bun:test | JUnit XML bridge via temp file | `bun add -d @gesetz/bun-test` |\n| `@gesetz/storybook` | test-storybook | Jest JSON bridge for Storybook interaction tests | `bun add -d @gesetz/storybook` |\n| `@gesetz/junit` | — | Shared JUnit XML parser (used by bun-test, Pest, PHPUnit) | `bun add -d @gesetz/junit` |\n\n### PHP\n\n| Package | Tool | What it does | Install |\n|---|---|---|---|\n| `@gesetz/phpstan` | PHPStan | Runs `analyse --error-format=json` | `bun add -d @gesetz/phpstan` |\n| `@gesetz/phpunit` | PHPUnit | JUnit XML bridge | `bun add -d @gesetz/phpunit` |\n| `@gesetz/pest` | Pest | JUnit XML bridge | `bun add -d @gesetz/pest` |\n\n### Usage example\n\n```ts\nimport { defineConfig } from 'gesetz';\nimport { eslint } from '@gesetz/eslint';\nimport { vitest } from '@gesetz/vitest';\nimport { oxlint } from '@gesetz/oxlint';\nimport { prettier } from '@gesetz/prettier';\nimport { phpstan } from '@gesetz/phpstan';\nimport { phpunit } from '@gesetz/phpunit';\n\nexport default defineConfig({\n  rules: [\n    oxlint({ pattern: 'src/**/*.{ts,tsx}', label: 'oxlint' }),\n    eslint({ pattern: 'src/**/*.{ts,tsx}', label: 'ESLint' }),\n    prettier({ pattern: 'src', label: 'Prettier' }),\n    vitest({ pattern: 'src', label: 'Vitest' }),\n    phpstan({ label: 'PHPStan' }),\n    phpunit({ label: 'PHPUnit' }),\n  ],\n});\n```\n\n---\n\n## What belongs in Gesetz vs. your language tools\n\n**Use Gesetz's built-in checks for:**\n\n- File pairing and directory structure (every `Foo.tsx` needs a `Foo.stories.tsx`)\n- Import discipline (no cross-domain imports, required imports, layer constraints)\n- Cross-cutting patterns (no console logs, no empty catches, no magic numbers, no debug logging)\n- Monorepo architecture (layer constraints, circular dependency detection)\n- Security hygiene (hardcoded secrets, forbidden file patterns)\n- Structural conventions (naming, docstrings, export pairs, explicit return types)\n\n**Use adapters to wrap your existing tools for:**\n\n- Deep type-level analysis (ESLint with `@typescript-eslint`, PHPStan, oxlint with `--type-aware`) — including type-checked rules like `no-floating-promises` that Gesetz intentionally does not reimplement.\n- Test result mapping (Vitest, PHPUnit, Pest, bun:test)\n- Format checking (Prettier, oxfmt)\n- Storybook interaction tests\n\nGesetz's rule runner executes all of these concurrently and merges their violations into one report. You don't give up your tools — you just stop reading five different output formats.\n\n---\n\n## Scoring \u0026 thresholds\n\nEach category gets a score from **0 to 10**:\n\n```\nweighted = errors * 1.0 + warnings * 0.5 + infos * 0.1\nscore    = max(0, 10 - weighted)\n```\n\nA project **passes** when every category with rules is at or above its threshold. Default threshold is **7**.\n\n```ts\ndefineConfig({\n  thresholds: [\n    { category: 'strictness', minScore: 8 },\n    { category: 'security',   minScore: 9 },\n  ],\n})\n```\n\nOverride from CLI:\n\n```bash\ngesetz check --threshold 9\n```\n\n---\n\n## Exemptions\n\nSuppress violations with expiring waivers.\n\n```ts\ndefineConfig({\n  exemptions: [\n    // Suppress all rules for legacy code\n    { path: 'src/legacy/**', reason: 'Migration in progress', ticket: 'PROJ-123', until: '2026-08-01' },\n    // Suppress only one rule for a specific file\n    { path: 'src/generated/**', rule: 'no-god-file', reason: 'Auto-generated schemas are large by design' },\n  ],\n})\n```\n\nExpired exemptions automatically stop suppressing — violations surface again.\n\n---\n\n## Agent integration\n\nGesetz is designed for AI agents. Three features make it agent-native:\n\n1. **`gesetz skill`** — outputs a markdown skill file for your agent framework (Claude Code, Cursor, Devin, etc.)\n2. **`--format=json`** — structured output with per-rule guidance for automated fixing\n3. **`--no-interactive`** — fully non-interactive init with auto-detection and JSON receipts\n\n```bash\n# Agent bootstrap\ngesetz init --no-interactive --format=json\n\n# Agent quality check\ngesetz check --format=json --since HEAD\n```\n\n---\n\n---\n\n## How it works\n\nGesetz is built in three layers, with dependencies pointing **downward only**:\n\n```\n┌──────────────────────────────────────────────────────────────┐\n│  @gesetz/core     contracts + file-system primitives         │\n│                   (zero parser dependencies)                 │\n├──────────────────────────────────────────────────────────────┤\n│  language adapters  export a SyntaxBackend object            │\n│  @gesetz/typescript, @gesetz/php  (+ future: python, …)      │\n├──────────────────────────────────────────────────────────────┤\n│  tool adapters      wrap external linters / test runners     │\n│  eslint, oxlint, phpstan, vitest, prettier, …                │\n└──────────────────────────────────────────────────────────────┘\n```\n\n- **Core** defines service *contracts* — `FileSystem`, `SyntaxTree`, `ImportResolver`, `ProjectRoot`, `FileFilter` — and ships text/regex checks that work on any language. It has **no parser dependency**, so adding a new language never touches core.\n- **Language adapters** each export a `SyntaxBackend` — a plain object that extracts imports, calls, exports, and structure from source via a real parser. You declare which adapters you want in `defineConfig({ adapters })`, and core's `SyntaxTreeLive` routes every file to the right backend by extension.\n- **Tool adapters** wrap external CLI tools (ESLint, PHPStan, Vitest, …) and normalize their output into the same `Violation[]` shape.\n\n### The three parsing tools\n\n| Tool | Used for | Lives in |\n|---|---|---|\n| `oxc-parser` | JS/TS imports + exports (clean module specifiers) | `@gesetz/typescript` |\n| `@ast-grep/napi` + `@ast-grep/lang-*` | Function calls + structural declarations for all languages | each language adapter |\n| (none — text/regex) | Universal checks: file size, secrets, debug logging, magic numbers | `@gesetz/core` |\n\nThere is **no `ts-morph` and no `tree-sitter`** anywhere — every check that used them was either syntactic (and migrated to ast-grep/oxc-parser) or type-level (and delegated to `@gesetz/eslint`/`@gesetz/oxlint`, which ship type-checked versions). Core stays parser-free.\n\n### What this means for your config\n\nSome checks need a registered `SyntaxBackend` to do their job (e.g. `noDirectCalls`, `requireNamingConvention`, `defineArchitecture` for accurate import extraction). You opt in by listing backends in your config:\n\n```ts\nimport { defineConfig } from 'gesetz';\nimport { typescriptSyntaxBackend } from '@gesetz/typescript';\nimport { phpSyntaxBackend } from '@gesetz/php';\n\nexport default defineConfig({\n  adapters: [typescriptSyntaxBackend, phpSyntaxBackend],  // ← opt in to parsing\n  rules: [/* ... */],\n});\n```\n\n- **No adapters listed?** Universal text/regex checks still run; SyntaxTree-backed checks silently no-op on files with no registered backend (they don't crash). `defineArchitecture` and `noImportFrom` fall back to a JS/TS regex.\n- **Polyglot project?** Just list every backend you need — they coexist because `SyntaxTreeLive` routes by extension (no Layer conflicts).\n\n---\n\n## Monorepo setup\n\nGesetz supports per-workspace configs. Run from the workspace root:\n\n```bash\n# packages/web/gesetz.config.ts\ngesetz check --project-root packages/web\n\n# Or from the repo root\ngesetz check --project-root apps/api\n```\n\n---\n\n## Packages\n\n| Package | Layer | Description | When to install |\n|---|---|---|---|\n| `gesetz` | meta | **Start here.** Core primitives, rule runner, `defineConfig`, `select`, `defineArchitecture`, and the CLI. No heavy dependencies. | Always |\n| `@gesetz/core` | core | Types, runner, primitives, file-system checks, pattern checks, architecture rules, `SyntaxTree`/`ImportResolver` contracts (included by `gesetz`) | — |\n| `@gesetz/cli` | core | `gesetz` command-line interface (included by `gesetz`) | — |\n| `@gesetz/typescript` | language adapter | `typescriptSyntaxBackend` + AST checks (oxc-parser + ast-grep): export pairs, call shapes, JSX, i18n, `noTypedAny`, `noEnum`, … | TypeScript projects needing AST rules |\n| `@gesetz/php` | language adapter | `phpSyntaxBackend` + PHP checks (`@ast-grep/lang-php`): strict types, PSR-4, type hints, final classes, … | PHP projects needing AST rules |\n| `@gesetz/effect-ts` | language-specific checks | Effect-TS anti-pattern detection (ast-grep) | Effect-TS codebases |\n| `@gesetz/laravel` | language-specific presets | Laravel opinionated presets | Laravel projects |\n| `@gesetz/eslint` | tool adapter | ESLint adapter | JS/TS projects using ESLint |\n| `@gesetz/oxlint` | tool adapter | oxlint adapter | Projects using oxlint |\n| `@gesetz/oxfmt` | tool adapter | oxfmt adapter | Projects using oxfmt |\n| `@gesetz/prettier` | tool adapter | Prettier adapter | Projects using Prettier |\n| `@gesetz/vitest` | tool adapter | Vitest adapter | Projects using Vitest |\n| `@gesetz/bun-test` | tool adapter | bun:test adapter | Projects using Bun tests |\n| `@gesetz/storybook` | tool adapter | test-storybook adapter | Projects with Storybook |\n| `@gesetz/junit` | tool adapter | Shared JUnit XML parser | Pulled in automatically by bun-test / PHPUnit / Pest |\n| `@gesetz/phpstan` | tool adapter | PHPStan adapter | PHP projects |\n| `@gesetz/phpunit` | tool adapter | PHPUnit adapter | PHP projects |\n| `@gesetz/pest` | tool adapter | Pest adapter | PHP projects |\n\n---\n\n## Philosophy\n\n- **One gate** — many tools, one report.\n- **Wrap, don't replace** — ESLint knows JS better than we do. PHPStan knows PHP better than we do. Gesetz wraps them so you read one output. Type-checked rules (e.g. `no-floating-promises`) are delegated to the adapters that already do them well.\n- **Core has zero parser deps** — `@gesetz/core` defines contracts; language adapters own the parsers. Adding a language never touches core.\n- **Cross-cutting first** — core's built-in checks focus on file structure, import discipline, and conventions that span every language. Language-specific depth lives in language adapters.\n- **Never crash the build** — a broken rule produces a warning, not a fatal error. SyntaxTree-backed checks silently skip files whose extension has no registered backend.\n- **Agent-native** — JSON output, skill files, guidance metadata.\n- **TypeScript-first** — your config is typed, your architecture is typed, your rules are typed. Rules are functions (no string dispatch, no global registry) — tree-shakeable and refactor-safe.\n- **Deterministic** — no global state, no random IDs, no module-level counters. Same code, same score, every time.\n- **Memory-bounded** — `FileSystem.glob` reads file content lazily on first access and ignores `node_modules`/`.git` by default.\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmateffy%2Fgesetz","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmateffy%2Fgesetz","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmateffy%2Fgesetz/lists"}